Environment Variables, Secrets, and Configuration

Bài 17 – Quản lý biến môi trường và cấu hình production trong Next.js Environment Variables, Secrets và Config

18/11/2025 DaiPhan
Bài 17 / 18

1. Giới thiệu về Environment Variables, Secrets và Configuration

Environment Variables và Secrets là thành phần quan trọng trong mọi ứng dụng production. Next.js hỗ trợ quản lý biến môi trường thông qua file .env và hệ thống env runtime trên các nền tảng như Vercel hay Cloudflare. Việc quản lý đúng giúp bảo mật hệ thống và tránh để lộ thông tin nhạy cảm.

Lợi ích của quản lý biến môi trường hiệu quả:

  • Bảo mật: Tách biệt thông tin nhạy cảm khỏi code
  • Flexibility: Dễ dàng thay đổi config mà không cần rebuild
  • Maintainability: Tập trung configuration trong một nơi
  • Scalability: Dễ dàng deploy trên nhiều môi trường khác nhau

Các trường hợp sử dụng phổ biến:

  • Database connection strings
  • API keys và secrets
  • Cấu hình third-party services
  • Feature flags
  • Environment-specific settings

2. Nội dung chính

2.1 Environment Variables trong Next.js

Next.js hỗ trợ nhiều loại file environment khác nhau:

  • .env.local: File mặc định cho local development
  • .env.production: Dành cho production environment
  • .env.development: Dành cho development environment
  • .env: File mặc định (ít dùng)

Quy tắc naming conventions:

  • Server-side variables: Không có prefix đặc biệt
  • Client-side variables: Phải có prefix NEXT_PUBLIC_
  • Secrets: Không bao giờ được sử dụng NEXT_PUBLIC_ prefix

2.2 Loading Order và Priority

Next.js load environment variables theo thứ tự ưu tiên:

  1. process.env (đã tồn tại)
  2. .env.$(NODE_ENV).local
  3. .env.local (không được include trong Git)
  4. .env.$(NODE_ENV)
  5. .env

2.3 Built-in Environment Variables

Next.js cung cấp một số biến môi trường built-in:

# Development
NODE_ENV=development
NEXT_TELEMETRY_DISABLED=1

# Build
NEXT_BUILD_ID=custom-build-id
NEXT_IGNORE_TYPECHECK=1

# Runtime
NEXT_SHARP_PATH=/custom/path/to/sharp
NEXT_DISABLE_MEMORY_WATCHER=1

2.4 Security Best Practices

❌ Điều cần tránh:

  • Không commit file .env.local chứa secrets
  • Không hardcode sensitive data trong code
  • Không sử dụng client-side variables cho sensitive data
  • Không expose database credentials trong client code

✅ Nên làm:

  • Sử dụng secret management services (Vercel, AWS Secrets Manager)
  • Rotate secrets regularly
  • Implement proper access controls
  • Monitor và audit secret usage

2.5 Platform-Specific Configuration

Vercel Environment Variables:

# Production Environment
VERCEL_ENV=production
VERCEL_URL=your-app.vercel.app
VERCEL_GIT_PROVIDER=github

# Build Settings
VERCEL_BUILD_CACHE_ENABLED=1
VERCEL_FORCE_NO_BUILD_CACHE=0

Cloudflare Pages:

# Cloudflare-specific
CF_PAGES=1
CF_PAGES_BRANCH=main
CF_PAGES_URL=https://your-site.pages.dev

2.6 Advanced Configuration Options

Runtime Configuration:

// next.config.js
module.exports = {
  env: {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
  },
  publicRuntimeConfig: {
    // Available on both server and client
    staticFolder: '/static',
  },
  serverRuntimeConfig: {
    // Only available on server
    secretKey: process.env.SECRET_KEY,
  },
}

TypeScript Support:

// types/environment.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    DATABASE_URL: string;
    API_KEY_SECRET: string;
    NEXT_PUBLIC_API_URL: string;
  }
}

3. Ví dụ thực tế

3.1 Cấu hình Environment Variables cơ bản

# .env.local - Local development
DATABASE_URL=postgres://admin:password@localhost:5432/myapp
API_KEY_SECRET=sk_test_1234567890abcdef
JWT_SECRET=your-super-secret-jwt-key
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My Awesome App

# .env.production - Production environment
DATABASE_URL=postgres://prod_user:secure_password@prod-db.example.com:5432/prod_db
API_KEY_SECRET=sk_live_0987654321fedcba
JWT_SECRET=prod-jwt-secret-key-min-32-characters
NEXT_PUBLIC_API_URL=https://api.production.com
NEXT_PUBLIC_APP_NAME=My Production App

# .env.development - Development environment
DATABASE_URL=postgres://dev_user:dev_password@dev-db.example.com:5432/dev_db
API_KEY_SECRET=sk_dev_1122334455667788
JWT_SECRET=dev-jwt-secret-key-for-testing
NEXT_PUBLIC_API_URL=https://api.dev.example.com
NEXT_PUBLIC_APP_NAME=My Dev App

3.2 Database Configuration với Environment Variables

// lib/database.ts
import { Pool } from 'pg'

// Server-side only - không expose credentials
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
  max: 20, // Maximum number of clients in the pool
  idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
  connectionTimeoutMillis: 2000, // Return an error after 2 seconds if connection could not be established
})

export async function query(text: string, params?: any[]) {
  const start = Date.now()
  try {
    const res = await pool.query(text, params)
    const duration = Date.now() - start
    console.log('Executed query', { text, duration, rows: res.rowCount })
    return res
  } catch (error) {
    console.error('Database query error:', error)
    throw error
  }
}

export async function getClient() {
  return await pool.connect()
}

3.3 API Routes với Environment Variables

// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import jwt from 'jsonwebtoken'

// Secret key từ environment variable
const JWT_SECRET = process.env.JWT_SECRET!
const API_KEY = process.env.API_KEY_SECRET!

export async function GET(request: NextRequest) {
  try {
    // Validate API key từ header
    const apiKey = request.headers.get('x-api-key')
    if (apiKey !== API_KEY) {
      return NextResponse.json({ error: 'Invalid API key' }, { status: 401 })
    }

    // Validate JWT token
    const authHeader = request.headers.get('authorization')
    if (!authHeader?.startsWith('Bearer ')) {
      return NextResponse.json({ error: 'No token provided' }, { status: 401 })
    }

    const token = authHeader.split(' ')[1]
    const decoded = jwt.verify(token, JWT_SECRET) as { userId: string }

    // Lấy users từ database
    const users = await getUsersFromDatabase(decoded.userId)

    return NextResponse.json({
      success: true,
      users,
      timestamp: new Date().toISOString()
    })

  } catch (error) {
    console.error('API Error:', error)
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

async function getUsersFromDatabase(userId: string) {
  // Implementation here
  return [{ id: userId, name: 'John Doe' }]
}

3.4 Client Components với Public Environment Variables

// components/ApiClient.tsx
'use client'

import { useState, useEffect } from 'react'

export default function ApiClient() {
  const [data, setData] = useState<any>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  // Public environment variables - an toàn để dùng trong client
  const API_URL = process.env.NEXT_PUBLIC_API_URL
  const APP_NAME = process.env.NEXT_PUBLIC_APP_NAME

  useEffect(() => {
    fetchData()
  }, [])

  const fetchData = async () => {
    setLoading(true)
    setError(null)

    try {
      const response = await fetch(`${API_URL}/data`)
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const result = await response.json()
      setData(result)
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred')
    } finally {
      setLoading(false)
    }
  }

  return (
    <div className="p-4 border rounded-lg">
      <h2 className="text-xl font-bold mb-2">{APP_NAME} - API Client</h2>
      <p className="text-sm text-gray-600 mb-4">API URL: {API_URL}</p>
      
      {loading && <div className="text-blue-600">Loading...</div>}
      {error && <div className="text-red-600">Error: {error}</div>}
      {data && (
        <div className="bg-gray-100 p-3 rounded">
          <pre className="text-sm">{JSON.stringify(data, null, 2)}</pre>
        </div>
      )}
      
      <button 
        onClick={fetchData}
        disabled={loading}
        className="mt-4 px-4 py-2 bg-blue-500 text-white rounded disabled:bg-gray-300"
      >
        Refresh Data
      </button>
    </div>
  )
}

3.5 Advanced next.config.js Configuration

// next.config.js
const { PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_BUILD } = require('next/constants')

module.exports = (phase, { defaultConfig }) => {
  // Environment-specific configurations
  const isDev = phase === PHASE_DEVELOPMENT_SERVER
  const isProd = phase === PHASE_PRODUCTION_BUILD

  const env = {
    CUSTOM_KEY: process.env.CUSTOM_KEY,
    ENVIRONMENT: isDev ? 'development' : isProd ? 'production' : 'other',
    VERSION: process.env.npm_package_version || '1.0.0',
  }

  return {
    ...defaultConfig,
    env,
    
    // Security headers
    async headers() {
      return [
        {
          source: '/:path*',
          headers: [
            {
              key: 'X-Frame-Options',
              value: 'DENY'
            },
            {
              key: 'X-Content-Type-Options',
              value: 'nosniff'
            },
            {
              key: 'Referrer-Policy',
              value: 'strict-origin-when-cross-origin'
            }
          ]
        }
      ]
    },

    // Redirects
    async redirects() {
      return [
        {
          source: '/old-path',
          destination: '/new-path',
          permanent: true,
        }
      ]
    },

    // Rewrites
    async rewrites() {
      return [
        {
          source: '/api/:path*',
          destination: `${process.env.API_BASE_URL}/:path*`,
        }
      ]
    },

    // Image optimization
    images: {
      domains: [
        process.env.NEXT_PUBLIC_IMAGE_DOMAIN || 'localhost',
        'images.unsplash.com',
        'via.placeholder.com'
      ],
      formats: ['image/webp', 'image/avif'],
    },

    // Internationalization
    i18n: {
      locales: ['en', 'vi', 'ja'],
      defaultLocale: 'en',
      domains: [
        {
          domain: process.env.NEXT_PUBLIC_DOMAIN_EN || 'example.com',
          defaultLocale: 'en',
        },
        {
          domain: process.env.NEXT_PUBLIC_DOMAIN_VI || 'example.vn',
          defaultLocale: 'vi',
        }
      ]
    },

    // Experimental features
    experimental: {
      appDir: true,
      serverComponentsExternalPackages: ['pg', 'redis']
    },

    // Webpack configuration
    webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
      // Environment-specific webpack configs
      if (!dev) {
        config.optimization.minimize = true
      }
      
      return config
    },
  }
}

3.6 Environment Validation và Type Safety

// lib/env.ts
import { z } from 'zod'

// Define schema cho environment variables
const envSchema = z.object({
  // Server-side only
  DATABASE_URL: z.string().url(),
  API_KEY_SECRET: z.string().min(10),
  JWT_SECRET: z.string().min(32),
  
  // Client-side (public)
  NEXT_PUBLIC_API_URL: z.string().url(),
  NEXT_PUBLIC_APP_NAME: z.string().min(1),
  NEXT_PUBLIC_ENABLE_ANALYTICS: z.string().transform(val => val === 'true'),
  
  // Optional
  REDIS_URL: z.string().url().optional(),
  PORT: z.string().transform(Number).default('3000'),
})

// Validate environment variables
export const env = envSchema.parse(process.env)

// Type-safe environment access
export function getEnvVar(key: keyof typeof env): string {
  return env[key]
}

// Usage example
export function validateEnvironment() {
  try {
    const validatedEnv = envSchema.parse(process.env)
    console.log('Environment variables validated successfully')
    return validatedEnv
  } catch (error) {
    console.error('Invalid environment variables:', error)
    throw new Error('Environment validation failed')
  }
}

3.7 Multi-environment Setup

# scripts/setup-env.sh
#!/bin/bash

echo "Setting up environment variables..."

# Create .env files if they don't exist
if [ ! -f .env.local ]; then
  cp .env.example .env.local
  echo "Created .env.local from template"
fi

if [ ! -f .env.development ]; then
  cp .env.example .env.development
  echo "Created .env.development from template"
fi

if [ ! -f .env.production ]; then
  cp .env.example .env.production
  echo "Created .env.production from template"
fi

# Validate required variables
required_vars=("DATABASE_URL" "API_KEY_SECRET" "JWT_SECRET")

for var in "${required_vars[@]}"; do
  if [ -z "${!var}" ]; then
    echo "Warning: $var is not set"
  fi
done

echo "Environment setup complete!"
# .env.example - Template file
# Copy this file to create your environment files

# Database
DATABASE_URL=postgresql://user:password@localhost:5432/myapp

# API Configuration
API_KEY_SECRET=your-api-key-here
JWT_SECRET=your-jwt-secret-min-32-characters

# Public Variables (Client-side)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_APP_NAME=My Application
NEXT_PUBLIC_DOMAIN=example.com

# Optional Services
# REDIS_URL=redis://localhost:6379
# SENTRY_DSN=https://your-sentry-dsn
# STRIPE_SECRET_KEY=sk_test_...
# STRIPE_PUBLISHABLE_KEY=pk_test_...

# Feature Flags
# NEXT_PUBLIC_ENABLE_ANALYTICS=false
# NEXT_PUBLIC_ENABLE_DEBUG=false

4. Kiến thức trọng tâm

4.1 Security Best Practices

✅ Nên làm:

  • Luôn validate environment variables trước khi sử dụng
  • Sử dụng schema validation với thư viện như Zod hoặc Joi
  • Implement proper error handling cho missing variables
  • Document tất cả required environment variables
  • Sử dụng secret management services cho production
  • Rotate secrets regularly với automated processes

❌ Không nên làm:

  • Không bao giờ commit secrets vào version control
  • Không expose server-side variables trong client code
  • Không hardcode sensitive information
  • Không ignore environment validation errors
  • Không share secrets qua insecure channels (email, chat)

4.2 Common Pitfalls và Solutions

Problem 1: Variables not available at build time

// ❌ Sai - biến không có sẵn trong build time
const config = {
  apiUrl: process.env.API_URL || 'http://localhost:3000'
}

// ✅ Đúng - sử dụng trong function/component
function getConfig() {
  return {
    apiUrl: process.env.API_URL || 'http://localhost:3000'
  }
}

Problem 2: TypeScript errors với process.env

// ❌ Sai - TypeScript không biết về custom env vars
const apiKey = process.env.API_KEY

// ✅ Đúng - Define types hoặc validate
const apiKey = process.env.API_KEY as string
// Hoặc sử dụng validation
const apiKey = validateEnvVar('API_KEY', process.env.API_KEY)

Problem 3: Environment-specific behavior

// ❌ Sai - Logic phụ thuộc vào NODE_ENV
if (process.env.NODE_ENV === 'development') {
  // Development logic
}

// ✅ Đúng - Sử dụng explicit feature flags
if (process.env.NEXT_PUBLIC_ENABLE_DEBUG === 'true') {
  // Debug logic
}

4.3 Deployment Strategies

Vercel Deployment:

# 1. Set environment variables
vercel env add DATABASE_URL production
vercel env add API_KEY_SECRET production
vercel env add NEXT_PUBLIC_API_URL production

# 2. Deploy với specific environment
vercel --prod

# 3. Verify deployment
vercel logs your-app.vercel.app

Docker Deployment:

# Dockerfile
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm ci --only=production

# Copy application code
COPY . .

# Set environment (sẽ được override khi run)
ENV NODE_ENV=production
ENV PORT=3000

# Build application
RUN npm run build

# Expose port
EXPOSE 3000

# Start application
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - API_KEY_SECRET=${API_KEY_SECRET}
      - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
    env_file:
      - .env.production

4.4 Monitoring và Debugging

Environment Validation Script:

// scripts/validate-env.ts
import { env } from '@/lib/env'

function validateEnvironment() {
  const requiredVars = [
    'DATABASE_URL',
    'API_KEY_SECRET',
    'JWT_SECRET',
    'NEXT_PUBLIC_API_URL'
  ]
  
  const missing = requiredVars.filter(varName => !env[varName as keyof typeof env])
  
  if (missing.length > 0) {
    console.error('❌ Missing environment variables:', missing)
    process.exit(1)
  }
  
  console.log('✅ All environment variables are set')
}

validateEnvironment()

Runtime Environment Monitoring:

// lib/monitoring.ts
export function logEnvironmentInfo() {
  console.log('Environment Information:', {
    NODE_ENV: process.env.NODE_ENV,
    NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
    API_BASE_URL: process.env.NEXT_PUBLIC_API_URL,
    BUILD_TIME: new Date().toISOString()
  })
}

export function checkCriticalServices() {
  // Check database connection
  // Check API endpoints
  // Check external services
  return {
    database: checkDatabase(),
    api: checkAPI(),
    timestamp: new Date().toISOString()
  }
}

5. Bài tập nhanh

Bài tập 1: Complete Environment Setup

Tạo một complete environment configuration system:

Yêu cầu:

  1. Setup 3 môi trường: development, staging, production
  2. Implement environment validation với Zod
  3. Tạo utility functions cho type-safe env access
  4. Setup Docker configuration với environment variables
  5. Tạo deployment scripts cho mỗi môi trường

Gợi ý cấu trúc:

├── environments/
│   ├── development.env
│   ├── staging.env
│   └── production.env
├── scripts/
│   ├── validate-env.js
│   ├── deploy-dev.sh
│   └── deploy-prod.sh
├── docker/
│   ├── Dockerfile
│   └── docker-compose.yml
└── lib/
    └── env.ts

Bài tập 2: Secret Management System

Implement một secret management system:

Yêu cầu:

  1. Tạo API endpoint để rotate secrets
  2. Implement secret caching với Redis
  3. Setup audit logging cho secret access
  4. Tạo admin dashboard để manage secrets
  5. Implement secret encryption/decryption

Gợi ý implementation:

// lib/secrets.ts
export class SecretManager {
  async getSecret(key: string): Promise<string> {
    // Implement caching, encryption, audit logging
  }
  
  async rotateSecret(key: string): Promise<void> {
    // Implement secret rotation logic
  }
}

Bài tập 3: Feature Flag System

Tạo feature flag system dựa trên environment variables:

Yêu cầu:

  1. Define feature flags trong environment
  2. Create React hooks để sử dụng feature flags
  3. Implement A/B testing capabilities
  4. Setup feature flag analytics
  5. Create admin interface để manage flags

Gợi ý:

// hooks/useFeatureFlag.ts
export function useFeatureFlag(flagName: string): boolean {
  const flags = useContext(FeatureFlagContext)
  return flags[flagName] || false
}

6. Kết luận

Quản lý đúng biến môi trường là kỹ năng quan trọng cho mọi developer Next.js. Việc implement proper security practices, validation, và deployment strategies giúp bảo mật ứng dụng và tạo quy trình deploy chuẩn.

Key Takeaways:

  1. Security First: Luôn prioritize security khi handle sensitive data
  2. Validation: Implement proper validation cho tất cả environment variables
  3. Type Safety: Sử dụng TypeScript để prevent runtime errors
  4. Documentation: Document tất cả required variables và use cases
  5. Automation: Automate validation, deployment, và monitoring processes

Best Practices Summary:

  • ✅ Sử dụng schema validation cho environment variables
  • ✅ Implement proper error handling và logging
  • ✅ Sử dụng secret management services cho production
  • ✅ Setup monitoring và alerting cho environment issues
  • ✅ Document và maintain environment configuration
  • ✅ Test environment setup trong CI/CD pipeline

Khi nào nên sử dụng các approach khác nhau:

  • Simple Projects: Basic .env files với manual validation
  • Enterprise Projects: Secret management services với audit logging
  • Microservices: Centralized configuration management
  • Open Source: Template files với clear documentation

Environment variables và configuration management là foundation của mọi production application. Đầu tư thời gian để setup đúng ngay từ đầu sẽ save rất nhiều thời gian và prevent security issues trong tương lai.

18 bài học
Bài 17
Tiến độ hoàn thành 94%

Đã hoàn thành 17/18 bài học