如何配置环境变量?NEXTPUBLIC 前缀的作用是什么?

57 阅读3分钟

如何配置环境变量?NEXTPUBLIC 前缀的作用是什么?

环境变量配置

1. 环境变量文件

Next.js 支持多种环境变量文件,按优先级排序:

# .env.local (最高优先级,所有环境)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key
API_URL=http://localhost:3001

# .env.development (开发环境)
DEBUG=true
LOG_LEVEL=debug

# .env.production (生产环境)
DEBUG=false
LOG_LEVEL=error

# .env (默认,所有环境)
NODE_ENV=development

2. 环境变量类型

# .env.local
# 服务器端环境变量
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-super-secret-jwt-key
API_SECRET_KEY=your-api-secret-key

# 客户端环境变量 (NEXT_PUBLIC_ 前缀)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My App
NEXT_PUBLIC_VERSION=1.0.0

# 构建时环境变量
NEXT_PUBLIC_BUILD_TIME=2024-01-01
NEXT_PUBLIC_GIT_COMMIT=abc123

NEXTPUBLIC 前缀的作用

1. 客户端可访问性

// 服务器端环境变量 - 只在服务器端可用
// .env.local
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key

// 客户端环境变量 - 在客户端和服务器端都可用
// .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My App
// 服务器端组件 - 可以访问所有环境变量
// app/api/users/route.js
export async function GET() {
  // ✅ 可以访问服务器端环境变量
  const dbUrl = process.env.DATABASE_URL
  const jwtSecret = process.env.JWT_SECRET

  // ✅ 也可以访问客户端环境变量
  const apiUrl = process.env.NEXT_PUBLIC_API_URL

  return NextResponse.json({ message: 'Hello' })
}

// 客户端组件 - 只能访问 NEXT_PUBLIC_ 前缀的变量
;('use client')
export default function ClientComponent() {
  // ❌ 无法访问服务器端环境变量
  // const dbUrl = process.env.DATABASE_URL // undefined

  // ✅ 可以访问客户端环境变量
  const apiUrl = process.env.NEXT_PUBLIC_API_URL
  const appName = process.env.NEXT_PUBLIC_APP_NAME

  return (
    <div>
      <h1>{appName}</h1>
      <p>API URL: {apiUrl}</p>
    </div>
  )
}

2. 构建时替换

// 构建时,NEXT_PUBLIC_ 变量会被替换为实际值
// 客户端代码
const apiUrl = process.env.NEXT_PUBLIC_API_URL

// 构建后的代码 (如果 NEXT_PUBLIC_API_URL=https://api.example.com)
const apiUrl = 'https://api.example.com'

实际应用示例

1. 多环境配置

# .env.development
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_APP_NAME=My App (Dev)
DEBUG=true

# .env.production
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My App
DEBUG=false

# .env.staging
NEXT_PUBLIC_API_URL=https://staging-api.example.com
NEXT_PUBLIC_APP_NAME=My App (Staging)
DEBUG=true
// lib/config.js
export const config = {
  apiUrl: process.env.NEXT_PUBLIC_API_URL,
  appName: process.env.NEXT_PUBLIC_APP_NAME,
  debug: process.env.DEBUG === 'true',
  isDevelopment: process.env.NODE_ENV === 'development',
  isProduction: process.env.NODE_ENV === 'production',
}

2. 客户端配置

// app/components/ConfigProvider.jsx
'use client'
import { createContext, useContext } from 'react'

const ConfigContext = createContext()

export function ConfigProvider({ children }) {
  const config = {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    appName: process.env.NEXT_PUBLIC_APP_NAME,
    version: process.env.NEXT_PUBLIC_VERSION,
    buildTime: process.env.NEXT_PUBLIC_BUILD_TIME,
  }

  return (
    <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>
  )
}

export function useConfig() {
  const context = useContext(ConfigContext)
  if (!context) {
    throw new Error('useConfig must be used within ConfigProvider')
  }
  return context
}
// app/components/ApiClient.jsx
'use client'
import { useConfig } from './ConfigProvider'

export function ApiClient() {
  const { apiUrl } = useConfig()

  const fetchData = async () => {
    const response = await fetch(`${apiUrl}/data`)
    return response.json()
  }

  return (
    <div>
      <p>API URL: {apiUrl}</p>
      <button onClick={fetchData}>Fetch Data</button>
    </div>
  )
}

3. 服务器端配置

// lib/server-config.js
export const serverConfig = {
  databaseUrl: process.env.DATABASE_URL,
  jwtSecret: process.env.JWT_SECRET,
  apiSecretKey: process.env.API_SECRET_KEY,
  nodeEnv: process.env.NODE_ENV,
}

// 验证必需的环境变量
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', 'API_SECRET_KEY']

for (const envVar of requiredEnvVars) {
  if (!process.env[envVar]) {
    throw new Error(`Missing required environment variable: ${envVar}`)
  }
}
// app/api/config/route.js
import { serverConfig } from '@/lib/server-config'

export async function GET() {
  // 只返回客户端需要的配置
  return NextResponse.json({
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    appName: process.env.NEXT_PUBLIC_APP_NAME,
    version: process.env.NEXT_PUBLIC_VERSION,
  })
}

高级配置

1. 动态环境变量

// next.config.js
const nextConfig = {
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
    BUILD_TIME: new Date().toISOString(),
  },

  publicRuntimeConfig: {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    appName: process.env.NEXT_PUBLIC_APP_NAME,
  },

  serverRuntimeConfig: {
    secret: process.env.JWT_SECRET,
    databaseUrl: process.env.DATABASE_URL,
  },
}

2. 环境变量验证

// lib/env-validation.js
import { z } from 'zod'

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  NEXT_PUBLIC_API_URL: z.string().url(),
  NEXT_PUBLIC_APP_NAME: z.string().min(1),
})

export const env = envSchema.parse(process.env)

3. 条件环境变量

// lib/conditional-env.js
export const getApiUrl = () => {
  if (process.env.NODE_ENV === 'production') {
    return process.env.NEXT_PUBLIC_API_URL
  }

  if (process.env.NODE_ENV === 'development') {
    return process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'
  }

  return 'http://localhost:3001'
}

export const getDatabaseUrl = () => {
  if (process.env.NODE_ENV === 'test') {
    return process.env.TEST_DATABASE_URL
  }

  return process.env.DATABASE_URL
}

最佳实践

1. 环境变量组织

# .env.example (模板文件)
# 服务器端环境变量
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key
API_SECRET_KEY=your-api-secret-key

# 客户端环境变量
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My App
NEXT_PUBLIC_VERSION=1.0.0

# 可选环境变量
DEBUG=false
LOG_LEVEL=info

2. 类型安全

// types/env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test'
    DATABASE_URL: string
    JWT_SECRET: string
    API_SECRET_KEY: string
    NEXT_PUBLIC_API_URL: string
    NEXT_PUBLIC_APP_NAME: string
    NEXT_PUBLIC_VERSION: string
  }
}

3. 安全考虑

// lib/secure-env.js
export const getSecureEnv = () => {
  // 检查是否在客户端
  if (typeof window !== 'undefined') {
    // 客户端只能访问 NEXT_PUBLIC_ 变量
    return {
      apiUrl: process.env.NEXT_PUBLIC_API_URL,
      appName: process.env.NEXT_PUBLIC_APP_NAME,
    }
  }

  // 服务器端可以访问所有变量
  return {
    databaseUrl: process.env.DATABASE_URL,
    jwtSecret: process.env.JWT_SECRET,
    apiSecretKey: process.env.API_SECRET_KEY,
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    appName: process.env.NEXT_PUBLIC_APP_NAME,
  }
}

总结

环境变量配置要点:

文件优先级

  • .env.local (最高优先级)
  • .env.development / .env.production
  • .env (默认)

NEXTPUBLIC 前缀

  • 客户端可访问
  • 构建时替换
  • 公开可见

最佳实践

  • 使用 .env.example 模板
  • 验证必需的环境变量
  • 区分客户端和服务器端变量
  • 使用类型安全
  • 注意安全性

安全考虑

  • 敏感信息不要使用 NEXT_PUBLIC_ 前缀
  • 客户端环境变量是公开的
  • 使用服务器端环境变量存储敏感信息