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

Next.js 13.4:如何安全传递服务端环境变量至客户端组件?

解决Next.js客户端组件中HCaptcha Sitekey的安全获取问题

你的核心问题是:客户端组件需要HCaptcha的sitekey,但直接使用非NEXT_PUBLIC_前缀的环境变量无法读取,添加前缀又会暴露密钥;尝试嵌套服务端组件失败,因为Next.js不允许客户端组件嵌套服务端组件,但可以通过服务端组件作为父级传递props或API路由间接获取两种方式解决。

方案一:通过服务端API路由获取Sitekey

利用Next.js API路由运行在服务端的特性,安全读取环境变量后返回给客户端:

  1. 创建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 });
    }
    
  2. 修改客户端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传递给客户端子组件,避免嵌套限制:

  1. 创建服务端父页面组件(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} />;
    }
    
  2. 将原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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 13:07:54