在Next.js 13引入App Router架构后,传统Pages Router中的许多数据获取方法,例如getStaticProps、getServerSideProps等,已被新的范式所取代。这导致开发者在迁移或新建项目时,在处理环境变量和数据获取方面遇到新的挑战,尤其是在集成第三方服务如Stripe时,常见的“undefined”错误往往源于对新架构理解的不足。
Next.js 13 App Router的核心理念是默认使用Server Components。这意味着组件默认在服务器上渲染,可以直接访问服务器端资源(如数据库、文件系统、环境变量),并进行异步操作。
当您在客户端组件中尝试使用 getStaticProps 或直接访问服务器端环境变量(如 process.env.STRIPE_SECRET_KEY)时,就会出现 undefined 的错误,因为这些操作只在服务器端有效。
在集成Stripe时,API密钥的安全性至关重要。Stripe提供了两种类型的密钥:
在您的 .env.local 文件中:
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = pk_test_apikeycontinues STRIPE_SECRET_KEY = sk_test_apikeycontinues
当您在客户端组件中尝试 Stripe(process.env.STRIPE_SECRET_KEY) 时,process.env.STRIPE_SECRET_KEY 将会是 undefined,因为 Next.js 默认不会将非 NEXT_PUBLIC_ 前缀的环境变量暴露给客户端。即使您尝试硬编码秘密密钥到客户端组件中,这也会带来严重的安全风险,因为它会被打包到客户端代码中,任何人都可以查看。
为了安全且正确地获取Stripe产品数据,我们应该始终在服务器端处理涉及 STRIPE_SECRET_KEY 的操作。以下是两种推荐的方法:
这是App Router中最直接且
推荐的方式。您可以在一个Server Component中直接初始化Stripe并获取数据。
./app/utils/stripe.js (服务器端 Stripe 实例)
// 这个文件只应在服务器端被导入和使用
import Stripe from 'stripe';
if (!process.env.STRIPE_SECRET_KEY) {
throw new Error('STRIPE_SECRET_KEY is not defined');
}
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2025-10-16', // 建议指定API版本
});./app/page.js (Server Component)
// 这是一个Server Component,默认在服务器端执行
import { stripe } from "./utils/stripe"; // 安全导入,因为只在服务器端使用
// 导入Client Component
import ProductDisplay from './components/ProductDisplay';
export default async function HomePage() {
let products = [];
try {
const inventory = await stripe.products.list({
limit: 5,
});
products = inventory.data; // Stripe API返回的数据通常在data属性中
console.log("Fetched products (server):", products);
} catch (error) {
console.error("Error fetching products from Stripe:", error);
}
return (
我们的产品
{/* 将数据传递给Client Component进行渲染 */}
);
}./app/components/ProductDisplay.js (Client Component)
'use client'; // 声明这是一个Client Component
export default function ProductDisplay({ products }) {
return (
{products && products.length > 0 ? (
暂无产品。
)} ); }如果您的数据需要在客户端组件中动态获取,或者需要在多个页面/组件中复用,可以创建一个Next.js API路由来处理Stripe API调用。
./app/api/products/route.js (API Route)
// 这是一个服务器端API路由
import { NextResponse } from 'next/server';
import { stripe } from '../../utils/stripe'; // 安全导入
export async function GET() {
try {
const inventory = await stripe.products.list({
limit: 5,
});
return NextResponse.json(inventory.data);
} catch (error) {
console.error("Error fetching products from Stripe API route:", error);
return NextResponse.json({ error: 'Failed to fetch products' }, { status: 500 });
}
}./app/page.js (Client Component 示例)
'use client';
import { useEffect, useState } from 'react';
export default function Home() {
const [products, setProducts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchProducts() {
try {
const response = await fetch('/api/products'); // 调用API路由
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProducts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchProducts();
}, []);
if (loading) return 加载产品中...
;
if (error) return 加载失败: {error}
;
return (
我们的产品
{products.length > 0 ? (
暂无产品。
)} ); }Next.js 13的 use Hook 允许在客户端组件中直接使用 Promise,但官方文档指出,在客户端组件中直接包装 fetch 可能会导致多次重新渲染,目前不推荐。对于客户端数据获取,更推荐使用像 SWR 或 React Query 这样的第三方库,它们提供了缓存、去重、错误处理等高级功能。
然而,如果您希望在不引入额外库的情况下,利用 use Hook 并避免重复请求,可以实现一个简单的缓存机制:
// queryClient.ts (或者 .js) // 这是一个用于在客户端组件中缓存Promise的实用函数 const fetchMap = new Map(); /** * 缓存并执行异步查询,避免在客户端组件中重复发起请求。 * @param name 缓存的唯一标识符。 * @param query 返回Promise的函数。 * @returns 缓存的Promise结果。 */ export function queryClient ( name: string, query: () => Promise ): Promise { if (!fetchMap.has(name)) { fetchMap.set(name, query()); } return fetchMap.get(name)!; }
在客户端组件中使用 queryClient:
'use client';
import { use } from 'react'; // 从React中导入use Hook
import { queryClient } from '../utils/queryClient'; // 导入缓存函数
// 假设我们有一个API路由 /api/product?slug=some-slug
// 或者您可以通过POST请求发送slug
async function fetchProductBySlug(slug) {
const response = await fetch(`/api/product?slug=${slug}`);
if (!response.ok) {
throw new Error('Failed to fetch product');
}
return response.json();
}
export default function ProductDetailPage({ slug }) {
// 使用queryClient缓存fetchProductBySlug的Promise
// 'product-detail-${slug}' 作为缓存的唯一名称
const { product } = use(
queryClient(
`product-detail-${slug}`,
() => fetchProductBySlug(slug)
)
);
if (!product) {
return 产品加载中...
; // use hook会处理pending状态
}
return (
{product.name}
{product.description}
价格: ${product.price / 100}
);
}注意事项:
遵循这些原则,您将能够更安全、高效地在Next.js 13 App Router中集成Stripe并管理数据获取。