如何在Next.js 13的_app.js中安全使用Storyblok私有访问令牌?
在Next.js 13的_app.js中隐藏Storyblok访问令牌的可行方案
问题背景
我正在用Storyblok (v2) + Next.js (v13)搭博客,清楚带NEXT_PUBLIC前缀的环境变量会暴露给浏览器,所以不想把访问令牌泄露出去。之前在组件里通过API路由实现过令牌隐藏,但不确定能不能在_app.js这类页面文件里这么做。查了Next.js文档还是没搞明白怎么在_app.js里实现,之前项目里成功做过类似的隐藏操作。
我已经试过这几种方法,但都没成功:
- 在根目录
.env里加不带NEXT_PUBLIC前缀的访问令牌,然后在next.config.js里配置env字段,接着在_app.js里用process.env.ACCESS_TOKEN调用,结果令牌还是暴露在页面里; - 只在
.env里加无NEXT_PUBLIC前缀的变量,不修改next.config.js,尝试在_app.js里通过getStaticProps传递token,报错“token未定义”; - 调整
MyApp组件参数后尝试获取props.token,报错“Cannot read properties of undefined (reading 'token')”。
想请教:在_app.js里实现隐藏访问令牌可行吗?如果可行,正确的做法是什么?也可以提供相关参考要点。
解决方案
在Next.js 13的Pages Router中,_app.js默认是客户端组件,直接在里面使用服务端环境变量会导致变量被打包到浏览器端,所以必须把令牌的使用逻辑放到服务端。下面是两种可行的方案:
方案1:通过API路由代理所有Storyblok请求
核心思路是:用Next.js的API路由作为中间层,所有客户端到Storyblok的请求都先经过这个API路由,令牌只在服务端的API路由里使用,不会暴露给浏览器。
- 创建API路由文件
pages/api/storyblok/[...path].js,用来代理所有Storyblok请求:
// pages/api/storyblok/[...path].js export default async function handler(req, res) { const { path } = req.query; // 拼接Storyblok的官方接口地址,带上服务端环境变量里的令牌 const storyblokApiUrl = `https://api.storyblok.com/v2/cdn/${path.join('/')}?token=${process.env.STORYBLOK_ACCESS_TOKEN}`; try { const response = await fetch(storyblokApiUrl); const data = await response.json(); res.status(response.status).json(data); } catch (err) { res.status(500).json({ error: '请求Storyblok数据失败' }); } }
- 在
_app.js里调用这个API路由,而不是直接请求Storyblok的官方接口:
// pages/_app.js import { useEffect, useState } from 'react'; function MyApp({ Component, pageProps }) { const [storyData, setStoryData] = useState(null); useEffect(() => { async function fetchStoryData() { // 示例:获取首页内容,根据你的需求调整API路径 const res = await fetch('/api/storyblok/stories/home'); const data = await res.json(); setStoryData(data); } fetchStoryData(); }, []); return <Component {...pageProps} storyData={storyData} />; } export default MyApp;
方案2:用getServerSideProps在服务端处理令牌(仅SSR场景)
如果你的博客是服务器端渲染(SSR)模式,可以在_app.js中添加getServerSideProps,在服务端用令牌请求数据后,把数据传递给组件——注意绝对不要把令牌本身传给客户端,只传处理后的数据。
// pages/_app.js function MyApp({ Component, pageProps, storyData }) { // 直接使用服务端获取的数据,不用碰令牌 return <Component {...pageProps} storyData={storyData} />; } export async function getServerSideProps() { // 服务端环境下直接使用无NEXT_PUBLIC前缀的变量,不会暴露 const storyblokRes = await fetch(`https://api.storyblok.com/v2/cdn/stories/home?token=${process.env.STORYBLOK_ACCESS_TOKEN}`); const storyData = await storyblokRes.json(); return { props: { storyData // 传递数据而非令牌 } }; } export default MyApp;
注意:_app.js添加getServerSideProps后,所有页面都会变成SSR模式,如果你需要静态生成(SSG)的页面,这个方案不适用。
为什么之前的方法失败?
- 方法1:
next.config.js里的env字段会把变量注入到客户端代码中,所以令牌会暴露,这是Next.js的设计逻辑; - 方法2:
_app.js不支持getStaticProps,这个方法只能在具体的页面文件(如pages/index.js)中使用; - 方法3:因为
getStaticProps在_app.js里不生效,所以props.token自然不存在,导致报错。
关键参考要点
- Next.js中,不带
NEXT_PUBLIC前缀的环境变量只能在服务端代码中使用(API路由、getServerSideProps、getStaticProps等); _app.js默认是客户端组件,直接在里面使用服务端环境变量会导致变量被打包到客户端,从而暴露;- 敏感令牌必须只在服务端代码中使用,客户端只通过API路由获取处理后的业务数据。
内容的提问来源于stack exchange,提问作者techmeowt
相关产品推荐
相关产品推荐

