如何在Next.js中通过getServerSideProps或middleware安全跨页传数据?
Next.js 服务端全局传参并隐藏API密钥的最佳方案
根据你的需求,核心是在服务端完成数据请求(隐藏密钥)并将数据全局传递给所有页面,以下分两种路由系统给出具体实现:
一、Pages Router(Next.js 12及以下,或13+兼容模式)
1. 利用 _app.tsx 的 getInitialProps
这是Pages Router里全局获取服务端数据的最直接方式,_app 是所有页面的入口,它的getInitialProps会在服务端执行,拿到的数据可以通过props传递给所有页面。
// pages/_app.tsx import type { AppProps } from 'next/app'; import { createContext, useContext } from 'react'; // 创建全局Context const GlobalDataContext = createContext(null); function MyApp({ Component, pageProps, globalData }: AppProps & { globalData: any }) { return ( <GlobalDataContext.Provider value={globalData}> <Component {...pageProps} /> </GlobalDataContext.Provider> ); } MyApp.getInitialProps = async (appContext) => { // 服务端执行,直接使用环境变量中的密钥,不会暴露给客户端 const API_KEY = process.env.SECRET_API_KEY; const res = await fetch(`https://api.example.com/data?key=${API_KEY}`); const globalData = await res.json(); // 保留页面自身的getInitialProps数据 const appProps = await App.getInitialProps(appContext); return { ...appProps, globalData }; }; // 导出Context供子组件使用 export const useGlobalData = () => useContext(GlobalDataContext); export default MyApp;
之后任意页面/组件可通过useGlobalData()直接读取全局数据。
2. 自定义API路由作为中间层
如果想解耦_app的请求逻辑,可以写一个服务端API路由,客户端页面调用自身API,密钥全程留在服务端:
// pages/api/get-global-data.ts export default async function handler(req, res) { const API_KEY = process.env.SECRET_API_KEY; const apiRes = await fetch(`https://api.example.com/data?key=${API_KEY}`); const data = await apiRes.json(); res.status(200).json(data); }
然后在_app.tsx中请求该API并存入Context:
// pages/_app.tsx import { useEffect, useState } from 'react'; import { createContext, useContext } from 'react'; const GlobalDataContext = createContext(null); function MyApp({ Component, pageProps }) { const [globalData, setGlobalData] = useState(null); useEffect(() => { const fetchData = async () => { const res = await fetch('/api/get-global-data'); const data = await res.json(); setGlobalData(data); }; fetchData(); }, []); if (!globalData) return <div>加载中...</div>; return ( <GlobalDataContext.Provider value={globalData}> <Component {...pageProps} /> </GlobalDataContext.Provider> ); } export const useGlobalData = () => useContext(GlobalDataContext); export default MyApp;
二、App Router(Next.js 13+)
App Router里优先用Server Component实现,官方推荐且更高效:
1. 根Layout作为Server Component获取全局数据
根Layout默认是Server Component,可直接在服务端请求数据,再通过Context传递给所有客户端组件:
// app/layout.tsx import { createContext } from 'react'; // 创建全局Context export const GlobalDataContext = createContext(null); // Server Component,无需加'use client' export default async function RootLayout({ children }) { // 服务端执行,直接读取环境变量密钥 const API_KEY = process.env.SECRET_API_KEY; const res = await fetch(`https://api.example.com/data?key=${API_KEY}`, { cache: 'no-store' }); const globalData = await res.json(); return ( <html lang="en"> <body> <GlobalDataContext.Provider value={globalData}> {children} </GlobalDataContext.Provider> </body> </html> ); }
客户端组件中读取Context:
// app/components/DataDisplay.tsx 'use client'; import { useContext } from 'react'; import { GlobalDataContext } from '../layout'; export default function DataDisplay() { const globalData = useContext(GlobalDataContext); return <div>全局数据:{globalData?.content}</div>; }
2. 中间件结合请求头传递(动态场景)
如果需要根据请求地域、用户信息动态获取数据,可通过Middleware把数据放到响应头,再在根Layout中读取:
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function middleware(request: NextRequest) { const API_KEY = process.env.SECRET_API_KEY; // 根据请求地域动态获取数据 const region = request.geo?.region || 'default'; const res = await fetch(`https://api.example.com/data?key=${API_KEY}®ion=${region}`); const globalData = await res.json(); // 将数据转为字符串存入自定义响应头(注意头大小限制,避免过大数据) const response = NextResponse.next(); response.headers.set('x-global-data', JSON.stringify(globalData)); return response; }
根Layout中读取响应头:
// app/layout.tsx import { headers } from 'next/headers'; import { createContext } from 'react'; export const GlobalDataContext = createContext(null); export default async function RootLayout({ children }) { const headersList = headers(); const globalDataStr = headersList.get('x-global-data'); const globalData = globalDataStr ? JSON.parse(globalDataStr) : null; return ( <html lang="en"> <body> <GlobalDataContext.Provider value={globalData}> {children} </GlobalDataContext.Provider> </body> </html> ); }
关键注意事项
- 所有涉及API密钥的请求必须在服务端环境执行(Server Component、API路由、Middleware、
getInitialProps等),环境变量不要加NEXT_PUBLIC_前缀,否则会暴露给客户端。 - 用Middleware传递数据时,注意浏览器对响应头的大小限制(一般为几KB),不要存放过大的数据集。
- App Router优先使用Server Component方案,性能优于客户端请求。
内容的提问来源于stack exchange,提问作者Xaero
相关产品推荐
相关产品推荐

