You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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的位置。

错误原因

  1. 模块顶层代码执行时机问题:API服务函数里的timeStamp、hash、query等变量在模块顶层定义,当该模块被客户端组件导入时,顶层代码会在客户端运行,直接访问了标记为server的环境变量。
  2. 客户端组件直接引用服务器变量:调用API的组件中直接打印了env.MARVEL_API_PRIVATE_KEY,这是服务器专属变量,不允许在客户端代码中访问。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:07:18