北纬的北 發表於 2025-11-13 09:45:00

在 Next.js 项目中安全配置环境变量:T3 Env

<h2 id="为什么需要专门的环境变量解决方案">为什么需要专门的环境变量解决方案?</h2>
<p>在 Next.js 应用开发中,环境变量管理一直是个棘手问题。传统的 <code>.env</code> 文件方式存在诸多痛点:</p>
<ul>
<li><strong>类型安全问题</strong>:环境变量没有类型检查,容易在运行时出错</li>
<li><strong>验证缺失</strong>:无法确保必需的环境变量都已正确配置</li>
<li><strong>客户端/服务端混淆</strong>:可能意外将敏感变量暴露到客户端</li>
<li><strong>团队协作困难</strong>:新成员不知道需要配置哪些环境变量</li>
</ul>
<p>T3 Env 正是为了解决这些问题而生,它提供了类型安全的环境变量管理方案。</p>
<div class="mermaid">flowchart TD
    A[".env&lt;br&gt;.env.local&lt;br&gt;…"] --&gt; B["src/env.js&lt;br&gt;createEnv({…})"]
    B --&gt; C{Zod Schema&lt;br&gt;校验层}
    C --&gt;|server variables| D["服务端代码&lt;br&gt;getStaticProps / API Routes …"]
    C --&gt;|client variables| E["客户端代码&lt;br&gt;浏览器 Bundle"]
    C --&gt;|shared variables| F["两端共享&lt;br&gt;NODE_ENV …"]
    C --&gt;|非法/缺失| G["运行时抛错&lt;br&gt;构建失败"]

    style B fill:#FFE082
    style C fill:#81C784
    style G fill:#EF5350,color:#fff
</div><h2 id="核心特性">核心特性</h2>
<h3 id="1-类型安全的环境变量">1. 类型安全的环境变量</h3>
<pre><code class="language-typescript">import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

const env = createEnv({
server: {
    DATABASE_URL: z.string().url(),
    API_SECRET: z.string().min(1),
},
client: {
    NEXT_PUBLIC_API_URL: z.string().url(),
},
runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    API_SECRET: process.env.API_SECRET,
    NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
</code></pre>
<h3 id="2-运行时验证">2. 运行时验证</h3>
<p>库在应用启动时会自动验证所有环境变量,如果缺少必需变量或类型不匹配,会立即抛出清晰错误,而不是在运行时神秘崩溃。</p>
<h3 id="3-客户端服务端自动隔离">3. 客户端/服务端自动隔离</h3>
<p>通过明确的配置区分,确保服务端敏感变量不会意外泄漏到客户端。</p>
<h2 id="配置指南">配置指南</h2>
<h3 id="基础安装与配置">基础安装与配置</h3>
<pre><code class="language-bash">npm install @t3-oss/env-nextjs zod
</code></pre>
<p>注意:T3 Env 提供了多个包, 如 <code>@t3-oss/env-nextjs</code> 和 <code>@t3-oss/env-core</code>,分别用于 Next.js 和普通 Node.js 项目。</p>
<p>创建 <code>env.js</code> 文件:</p>
<pre><code class="language-typescript">import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";

export const env = createEnv({
server: {
    // 服务端专用环境变量
    DATABASE_URL: z.string().url(),
    GITHUB_CLIENT_SECRET: z.string().min(1),
    NODE_ENV: z.enum(["development", "test", "production"]),
},
client: {
    // 客户端可访问的环境变量
    NEXT_PUBLIC_API_BASE_URL: z.string().url(),
    NEXT_PUBLIC_APP_VERSION: z.string().min(1),
},

// 运行时环境变量映射
runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
    NODE_ENV: process.env.NODE_ENV,
    NEXT_PUBLIC_API_BASE_URL: process.env.NEXT_PUBLIC_API_BASE_URL,
    NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION,
},

// 跳过某些环境变量的验证(可选)
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});
</code></pre>
<h3 id="高级验证场景">高级验证场景</h3>
<pre><code class="language-typescript">const env = createEnv({
server: {
    // 复杂验证规则
    PORT: z.string().regex(/^\d+$/).transform(Number),
    FEATURE_FLAGS: z.string().transform((str) =&gt; str.split(',')),
    MAX_UPLOAD_SIZE: z.string().default('10').transform(Number),
   
    // 条件验证
    DATABASE_URL: z.string().url().optional(),
    DATABASE_HOST: z.string().min(1).optional(),
}).refine(
    (data) =&gt; data.DATABASE_URL || data.DATABASE_HOST,
    "Either DATABASE_URL or DATABASE_HOST must be provided"
),
});
</code></pre>
<h2 id="实际应用">实际应用</h2>
<h3 id="api-路由中的使用">API 路由中的使用</h3>
<pre><code class="language-typescript">// pages/api/users.ts
import { env } from "../../env";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// 类型安全的环境变量访问
const databaseUrl = env.DATABASE_URL;
const apiSecret = env.API_SECRET;

// 业务逻辑...
res.status(200).json({ success: true });
}
</code></pre>
<h3 id="客户端组件中的使用">客户端组件中的使用</h3>
<pre><code class="language-typescript">// components/UserProfile.tsx
import { env } from "../env";

export function UserProfile() {
// 只能访问客户端环境变量
const apiUrl = env.NEXT_PUBLIC_API_BASE_URL;

return (
    &lt;div&gt;
      &lt;p&gt;API Base URL: {apiUrl}&lt;/p&gt;
    &lt;/div&gt;
);
}
</code></pre>
<h3 id="与-nextjs-配置集成">与 Next.js 配置集成</h3>
<pre><code class="language-typescript">// next.config.js
const { env } = require("./env");

/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
    CUSTOM_KEY: "value",
},
publicRuntimeConfig: {
    apiUrl: env.NEXT_PUBLIC_API_BASE_URL,
},
};

module.exports = nextConfig;
</code></pre>
<h2 id="真实案例">真实案例</h2>
<h3 id="create-t3-app">create-t3-app</h3>
<p>create-t3-app 是一个快速构建全栈 TypeScript 应用的脚手架,集成了 Next.js、tRPC、Prisma、Tailwind CSS 等现代工具。它以“强类型、可扩展、开发体验优先”为核心理念,帮助开发者快速搭建高质量的 Web 应用。</p>
<h3 id="nahida-template">nahida-template</h3>
<p>我搭建的 nahida-template,就用到了 @t3-oss/env-core。nahida-template 是基于 Elysia.js、Next.js 16 和 TypeScript 的现代全栈单仓模板,提供高性能与端到端类型安全支持。欢迎 star 和提建议。</p>
<pre><code class="language-ts">import { createEnv } from "@t3-oss/env-core"
import { z } from "zod"

export const env = createEnv({
shared: {
    NODE_ENV: z
      .enum(["development", "production", "test"])
      .default("development"),
    PORT: z.number().default(3001),
},

/**
   * Specify your server-side environment variables schema here. This way you can ensure the app
   * isn't built with invalid env vars.
   */
server: {
    NODE_ENV: z
      .enum(["development", "test", "production"])
      .default("development"),
    DATABASE_URL: z.string().url(),
    BETTER_AUTH_SECRET: z.string(),
    BETTER_AUTH_URL: z.string().url(),
    GITHUB_CLIENT_ID: z.string(),
    GITHUB_CLIENT_SECRET: z.string(),
},

/**
   * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
   * middlewares) or client-side so we need to destruct manually.
   */
runtimeEnv: {
    NODE_ENV: process.env.NODE_ENV,
    DATABASE_URL: process.env.DATABASE_URL,
    BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
    BETTER_AUTH_URL: process.env.BETTER_AUTH_URL,
    GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
    GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
    // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},

/**
   * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
   * useful for Docker builds.
   */
skipValidation: !!process.env.SKIP_ENV_VALIDATION,

/**
   * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
   * `SOME_VAR=''` will throw an error.
   */
emptyStringAsUndefined: true,
})
</code></pre>
<h3 id="_"></h3>
<h2 id="性能与安全考量">性能与安全考量</h2>
<h3 id="性能">性能</h3>
<ul>
<li>环境变量验证只在启动时进行一次</li>
<li>支持设置 <code>skipValidation: true</code> 跳过运行时验证</li>
<li>使用环境变量缓存避免重复初始化</li>
</ul>
<h3 id="安全最佳实践">安全最佳实践</h3>
<ul>
<li>永远不要将敏感信息提交到版本控制</li>
<li>使用不同的密钥用于开发、测试和生产环境</li>
<li>定期轮换密钥和密码</li>
<li>使用密钥管理服务(如 AWS Secrets Manager)</li>
</ul>
<h2 id="总结">总结</h2>
<p><code>T3 Env</code> 提供了优雅的环境变量管理方案。通过类型安全、运行时验证和清晰的客户端/服务端分离,它显著提高了应用的可靠性和开发体验。</p>
<p>对于任何规模的 Next.js 项目,投资于健全的环境变量管理都会在项目的整个生命周期中带来丰厚的回报。</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li>https://env.t3.gg/ - T3 Env 官方文档</li>
<li>https://zod.dev/ - 掌握更复杂的验证模式</li>
<li>https://nextjs.org/docs/app/building-your-application/configuring/environment-variables - 了解框架原生支持</li>
<li>https://create.t3.gg/ - 探索完整的类型安全开发生态系统</li>
</ul><br><br>
来源:https://www.cnblogs.com/guangzan/p/19212998
頁: [1]
查看完整版本: 在 Next.js 项目中安全配置环境变量:T3 Env