Next.js 13.4:如何安全传递服务端环境变量至客户端组件?
解决Next.js客户端组件中HCaptcha Sitekey的安全获取问题
你的核心问题是:客户端组件需要HCaptcha的sitekey,但直接使用非NEXT_PUBLIC_前缀的环境变量无法读取,添加前缀又会暴露密钥;尝试嵌套服务端组件失败,因为Next.js不允许客户端组件嵌套服务端组件,但可以通过服务端组件作为父级传递props或API路由间接获取两种方式解决。
方案一:通过服务端API路由获取Sitekey
利用Next.js API路由运行在服务端的特性,安全读取环境变量后返回给客户端:
创建API路由文件:
- 若使用App Router,新建
app/api/hcaptcha-sitekey/route.js - 若使用Pages Router,新建
pages/api/hcaptcha-sitekey.js
代码示例:
// App Router 版本 export async function GET() { const sitekey = process.env.HCAPTCHA_SITEKEY; if (!sitekey) { return new Response(JSON.stringify({ error: 'Sitekey未配置' }), { status: 500 }); } return new Response(JSON.stringify({ sitekey }), { status: 200 }); } // Pages Router 版本 export default function handler(req, res) { const sitekey = process.env.HCAPTCHA_SITEKEY; if (!sitekey) { return res.status(500).json({ error: 'Sitekey未配置' }); } res.status(200).json({ sitekey }); }- 若使用App Router,新建
修改客户端Contact组件,添加获取sitekey的逻辑:
'use client' import { useState, useEffect } from 'react' import { useForm } from 'react-hook-form' import axios from 'axios' import HCaptcha from '@hcaptcha/react-hcaptcha' export default function Contact(){ const [token, setToken] = useState(null) const [msgSent, setMsgSent] = useState(null) const [sitekey, setSitekey] = useState(null) const [loading, setLoading] = useState(true) const { register, handleSubmit, formState: { errors }, } = useForm(); // 从API路由获取sitekey useEffect(() => { const fetchSitekey = async () => { try { const response = await axios.get('/api/hcaptcha-sitekey'); setSitekey(response.data.sitekey); } catch (error) { console.error('获取sitekey失败:', error); } finally { setLoading(false); } }; fetchSitekey(); }, []); const onSubmit = async (values, e) => { e.preventDefault() if (!token) { return alert('需要完成验证码验证') } try { axios.post('/api/email/send', { name: values.name, email: values.email, message: values.message }) .then(function (response) { if (response.status <= 250) { setMsgSent(true) e.target.reset() } }) .catch(function (error) { //console.log(error); }); } catch (error) { console.log(error) } }; if (loading) return <div>加载中...</div>; if (!sitekey) return <div>验证码加载失败</div>; return( <div> <form onSubmit={handleSubmit(onSubmit)}> <div> <div> {msgSent && <span>已发送</span> } {!msgSent && <span>尚未发送</span> } </div> <div>表单字段</div> <div> <HCaptcha sitekey={ sitekey } onVerify={ setToken } onError={ () => setToken(null) } onExpire={ () => setToken(null) } /> </div> <input type="submit" value="发送" /> </div> </form> </div> ) }
方案二:服务端父组件传递Sitekey给客户端子组件
Next.js允许服务端组件作为父组件,将服务端获取的sitekey通过props传递给客户端子组件,避免嵌套限制:
创建服务端父页面组件(App Router下页面默认是服务端组件,比如
app/contact/page.js):// app/contact/page.js import ContactForm from './ContactForm'; export default async function ContactPage() { const sitekey = process.env.HCAPTCHA_SITEKEY; if (!sitekey) { return <div>验证码未配置</div>; } return <ContactForm sitekey={sitekey} />; }将原Contact组件改为客户端子组件
app/contact/ContactForm.js,接收sitekey props:'use client' import { useState } from 'react' import { useForm } from 'react-hook-form' import axios from 'axios' import HCaptcha from '@hcaptcha/react-hcaptcha' export default function ContactForm({ sitekey }){ const [token, setToken] = useState(null) const [msgSent, setMsgSent] = useState(null) const { register, handleSubmit, formState: { errors }, } = useForm(); const onSubmit = async (values, e) => { e.preventDefault() if (!token) { return alert('需要完成验证码验证') } try { axios.post('/api/email/send', { name: values.name, email: values.email, message: values.message }) .then(function (response) { if (response.status <= 250) { setMsgSent(true) e.target.reset() } }) .catch(function (error) { //console.log(error); }); } catch (error) { console.log(error) } }; return( <div> <form onSubmit={handleSubmit(onSubmit)}> <div> <div> {msgSent && <span>已发送</span> } {!msgSent && <span>尚未发送</span> } </div> <div>表单字段</div> <div> <HCaptcha sitekey={ sitekey } onVerify={ setToken } onError={ () => setToken(null) } onExpire={ () => setToken(null) } /> </div> <input type="submit" value="发送" /> </div> </form> </div> ) }
两种方案对比:
- 方案一适合组件需多处复用、或需动态获取sitekey的场景;
- 方案二更直接,减少一次API请求,利用Next.js服务端组件特性实现安全传递。
内容的提问来源于stack exchange,提问作者Nathan Bernard
相关产品推荐
相关产品推荐

