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

为什么需要专门的环境变量解决方案?

在 Next.js 应用开发中,环境变量管理一直是个棘手问题。传统的 .env 文件方式存在诸多痛点:

  • 类型安全问题:环境变量没有类型检查,容易在运行时出错
  • 验证缺失:无法确保必需的环境变量都已正确配置
  • 客户端/服务端混淆:可能意外将敏感变量暴露到客户端
  • 团队协作困难:新成员不知道需要配置哪些环境变量

T3 Env 正是为了解决这些问题而生,它提供了类型安全的环境变量管理方案。
flowchart TD A[".env<br>.env.local<br>..."] --> B["src/env.js<br>createEnv({...})"] B --> C{Zod Schema<br>校验层} C -->|server variables| D["服务端代码<br>getStaticProps / API Routes ..."] C -->|client variables| E["客户端代码<br>浏览器 Bundle"] C -->|shared variables| F["两端共享<br>NODE_ENV ..."] C -->|非法/缺失| G["运行时抛错<br>构建失败"] style B fill:#FFE082 style C fill:#81C784 style G fill:#EF5350,color:#fff

核心特性

1. 类型安全的环境变量

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,
  },
});

2. 运行时验证

库在应用启动时会自动验证所有环境变量,如果缺少必需变量或类型不匹配,会立即抛出清晰错误,而不是在运行时神秘崩溃。

3. 客户端/服务端自动隔离

通过明确的配置区分,确保服务端敏感变量不会意外泄漏到客户端。

配置指南

基础安装与配置

bash 复制代码
npm install @t3-oss/env-nextjs zod

注意:T3 Env 提供了多个包, 如 @t3-oss/env-nextjs@t3-oss/env-core,分别用于 Next.js 和普通 Node.js 项目。

创建 env.js 文件:

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,
});

高级验证场景

typescript 复制代码
const env = createEnv({
  server: {
    // 复杂验证规则
    PORT: z.string().regex(/^\d+$/).transform(Number),
    FEATURE_FLAGS: z.string().transform((str) => 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) => data.DATABASE_URL || data.DATABASE_HOST,
    "Either DATABASE_URL or DATABASE_HOST must be provided"
  ),
});

实际应用

API 路由中的使用

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 });
}

客户端组件中的使用

typescript 复制代码
// components/UserProfile.tsx
import { env } from "../env";

export function UserProfile() {
  // 只能访问客户端环境变量
  const apiUrl = env.NEXT_PUBLIC_API_BASE_URL;
  
  return (
    <div>
      <p>API Base URL: {apiUrl}</p>
    </div>
  );
}

与 Next.js 配置集成

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;

真实案例

create-t3-app

create-t3-app 是一个快速构建全栈 TypeScript 应用的脚手架,集成了 Next.js、tRPC、Prisma、Tailwind CSS 等现代工具。它以"强类型、可扩展、开发体验优先"为核心理念,帮助开发者快速搭建高质量的 Web 应用。

nahida-template

我搭建的 nahida-template,就用到了 @t3-oss/env-core。nahida-template 是基于 Elysia.js、Next.js 16 和 TypeScript 的现代全栈单仓模板,提供高性能与端到端类型安全支持。欢迎 star 和提建议。

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,
})

性能与安全考量

性能

  • 环境变量验证只在启动时进行一次
  • 支持设置 skipValidation: true 跳过运行时验证
  • 使用环境变量缓存避免重复初始化

安全最佳实践

  • 永远不要将敏感信息提交到版本控制
  • 使用不同的密钥用于开发、测试和生产环境
  • 定期轮换密钥和密码
  • 使用密钥管理服务(如 AWS Secrets Manager)

总结

T3 Env 提供了优雅的环境变量管理方案。通过类型安全、运行时验证和清晰的客户端/服务端分离,它显著提高了应用的可靠性和开发体验。

对于任何规模的 Next.js 项目,投资于健全的环境变量管理都会在项目的整个生命周期中带来丰厚的回报。

参考资料

相关推荐
洞窝技术18 小时前
Next.js 不只是前端框架!我们用它搭了个发布中枢,让跨团队协作效率翻倍
前端·next.js
阿四7 天前
【Nextjs】为什么server action中在try/catch内写redirect操作会跳转失败?
前端·next.js
米诺zuo21 天前
前端react用到的next-auth/react库处理用户认证
next.js
JasperX23 天前
1700+ Emoji 怎么找?我花 2 天做了个搜索站 (已开源)
next.js
天蓝色的鱼鱼23 天前
Next.js的水合:静默的页面“唤醒”术
前端·react.js·next.js
鸡吃丸子25 天前
Next.js 入门指南
开发语言·javascript·next.js
Mintopia25 天前
⚙️ Next.js 缓存与队列:当数据与请求跳起“低延迟之舞”
前端·全栈·next.js
三木檾1 个月前
一文看懂 Next.js 数据获取与渲染策略:从 SSR 到 ISR 的取舍之道
next.js