如何用MSW测试含环境变量的API端点?Next.js Marvel API报错排查
解决Next.js中服务器环境变量被客户端访问的错误
问题场景
测试依赖环境变量的Marvel API端点时,触发错误:❌ Attempted to access a server-side environment variable on the client
错误定位在getHash函数访问env.MARVEL_API_PRIVATE_KEY的位置。
错误原因
- 模块顶层代码执行时机问题:API服务函数里的
timeStamp、hash、query等变量在模块顶层定义,当该模块被客户端组件导入时,顶层代码会在客户端运行,直接访问了标记为server的环境变量。 - 客户端组件直接引用服务器变量:调用API的组件中直接打印了
env.MARVEL_API_PRIVATE_KEY,这是服务器专属变量,不允许在客户端代码中访问。 - Next.js环境变量规则:
@t3-oss/env-nextjs中配置在server字段的变量,仅允许在服务器端代码(API路由、服务器组件、Server Action)中访问,客户端代码引用会触发报错。
修复步骤
1. 调整API服务函数结构,避免顶层访问服务器变量
把需要计算hash、query的逻辑移到函数内部,确保只有在函数执行时(服务器端)才访问环境变量:
import md5 from 'md5'; import { env } from '@/env'; const getTimestamp = () => Date.now().toString(); const getHash = (timeStamp: string) => md5(timeStamp + env.MARVEL_API_PRIVATE_KEY + env.MARVEL_API_PUBLIC_KEY); export const getCharacters = async () => { // 将计算逻辑移到函数内部,确保仅在服务器端执行 const timeStamp = getTimestamp(); const hash = getHash(timeStamp); const query = `ts=${timeStamp}&apikey=${env.MARVEL_API_PUBLIC_KEY}&hash=${hash}`; const url = `${env.MARVEL_API_URL}/characters?limit=50&${query}`; const response = await fetch(url); if (!response.ok) { throw new Error('获取角色列表失败'); } const data = await response.json(); // 补充await,修复原代码的异步问题 return data; };
2. 清理客户端组件中的服务器变量引用
移除组件中打印服务器环境变量的代码,避免客户端接触到敏感变量:
import { CharacterCard } from '@/app/components/Characters'; import { Character } from '@/models/character'; import { getCharacters } from '@/services/api'; import styles from './styles.module.css'; export const CharactersList = async () => { const characters = await getCharacters(); // 移除打印服务器环境变量的代码 // console.log('ENV', env.NODE_ENV, env.MARVEL_API_URL, env.MARVEL_API_PRIVATE_KEY, env.MARVEL_API_PUBLIC_KEY); return ( <section className={styles.charactersList}> {characters.data.results.map((character: Character) => ( <CharacterCard key={character.id} character={character} /> ))} </section> ); };
3. 推荐方案:将API调用移到专属API路由
更安全的做法是把Marvel API的调用逻辑封装到Next.js API路由中,彻底隔离客户端与服务器环境变量:
创建app/api/characters/route.ts:
import { NextResponse } from 'next/server'; import md5 from 'md5'; import { env } from '@/env'; const getTimestamp = () => Date.now().toString(); const getHash = (timeStamp: string) => md5(timeStamp + env.MARVEL_API_PRIVATE_KEY + env.MARVEL_API_PUBLIC_KEY); export async function GET() { const timeStamp = getTimestamp(); const hash = getHash(timeStamp); const query = `ts=${timeStamp}&apikey=${env.MARVEL_API_PUBLIC_KEY}&hash=${hash}`; const url = `${env.MARVEL_API_URL}/characters?limit=50&${query}`; const response = await fetch(url); if (!response.ok) { return NextResponse.json({ error: '获取角色列表失败' }, { status: response.status }); } const data = await response.json(); return NextResponse.json(data); }
修改组件调用API路由:
import { CharacterCard } from '@/app/components/Characters'; import { Character } from '@/models/character'; import styles from './styles.module.css'; export const CharactersList = async () => { const response = await fetch('/api/characters'); if (!response.ok) { throw new Error('获取角色列表失败'); } const characters = await response.json(); return ( <section className={styles.charactersList}> {characters.data.results.map((character: Character) => ( <CharacterCard key={character.id} character={character} /> ))} </section> ); };
4. 更新MSW处理器适配API路由
如果用MSW测试API调用,调整处理器路径为API路由地址:
import { HttpResponse, http } from 'msw'; export const handlers = [ http.get('/api/characters', () => { return HttpResponse.json( { data: { results: [ { id: 1011334, name: '3-D Man' } ] } }, { status: 200 }, ); }), ];
核心原则
服务器环境变量(如MARVEL_API_PRIVATE_KEY)属于敏感信息,必须仅在服务器端代码中访问。通过将API逻辑封装到服务器组件或API路由,确保客户端代码完全不接触这些敏感变量,同时符合Next.js的环境变量安全规则。
内容的提问来源于stack exchange,提问作者Francis Lagares
相关产品推荐
相关产品推荐

