Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Zmienne środowiskowe i konfiguracja produkcyjna

W Metropolii Quantum 2150 każdy system wymaga precyzyjnej konfiguracji środowiskowej. Tak jak w cyberpunkowym mieście każda dzielnica ma własne protokoły bezpieczeństwa, tak aplikacja Next.js potrzebuje różnych ustawień dla różnych środowisk — development, staging i production. Zmienne środowiskowe to klucz do bezpiecznego i elastycznego zarządzania konfiguracją.

Czym są zmienne środowiskowe?

Zmienne środowiskowe (environment variables) to pary klucz-wartość, które konfigurują zachowanie aplikacji bez modyfikacji kodu źródłowego. Dzięki nim możesz przechowywać:

  • Klucze API i sekrety (np. klucze do bazy danych, tokeny autoryzacji)
  • Adresy URL serwisów zewnętrznych
  • Flagi konfiguracyjne (np. tryb debug, poziom logowania)
  • Ustawienia specyficzne dla środowiska (development vs production)

Pliki .env w Next.js

Next.js ma wbudowane wsparcie dla plików

.env
. Framework automatycznie ładuje zmienne środowiskowe z kilku plików, w określonej kolejności priorytetów:

Hierarchia plików .env

1# Pliki .env w Next.js (od najwyższego priorytetu):
2.env.local          # Lokalne nadpisania - NIGDY nie commituj do git!
3.env.development    # Tylko dla npm run dev
4.env.production     # Tylko dla npm run build / npm start
5.env                # Domyślne wartości dla wszystkich środowisk

Każdy plik może nadpisywać wartości z plików o niższym priorytecie. Plik

.env.local
ma zawsze najwyższy priorytet i powinien być w
.gitignore
.

Tworzenie plików .env

1# .env - domyślne wartości (commitowane do repo)
2NEXT_PUBLIC_APP_NAME=QuantumCity
3NEXT_PUBLIC_API_URL=http://localhost:4000/api
4DATABASE_URL=mongodb://localhost:27017/quantum_dev
5JWT_SECRET=development-secret-key-change-in-production
6
7# .env.local - lokalne sekrety (NIE commituj!)
8DATABASE_URL=mongodb://user:secret@prod-cluster.mongodb.net/quantum
9JWT_SECRET=super-tajny-klucz-produkcyjny-2150
10OPENAI_API_KEY=sk-proj-abc123...
11STRIPE_SECRET_KEY=sk_live_abc123...
12
13# .env.production - ustawienia produkcyjne
14NEXT_PUBLIC_API_URL=https://api.quantum-city.com
15NEXT_PUBLIC_APP_NAME=QuantumCity Production
16NODE_ENV=production

Prefix NEXT_PUBLIC_ — klucz do bezpieczeństwa

To jedna z najważniejszych zasad w Next.js! Prefix

NEXT_PUBLIC_
determinuje, czy zmienna jest dostępna w przeglądarce:

1// Zmienne BEZ prefixu - dostępne TYLKO na serwerze
2// Nigdy nie trafiają do bundla przeglądarki!
3const dbUrl = process.env.DATABASE_URL;        // OK w Server Components
4const apiKey = process.env.OPENAI_API_KEY;     // OK w Route Handlers
5const secret = process.env.JWT_SECRET;         // OK w middleware
6
7// Zmienne Z prefixem NEXT_PUBLIC_ - dostępne WSZĘDZIE
8// Trafiają do bundla JavaScript w przeglądarce!
9const appName = process.env.NEXT_PUBLIC_APP_NAME;  // OK w Client Components
10const apiUrl = process.env.NEXT_PUBLIC_API_URL;    // OK wszędzie

Przykład użycia w komponentach

1// app/layout.tsx - Server Component
2export default function RootLayout({ children }: { children: React.ReactNode }) {
3  // Bezpieczne - zmienna serwerowa, nie trafia do przeglądarki
4  const dbUrl = process.env.DATABASE_URL;
5  console.log('Connecting to:', dbUrl);
6
7  return (
8    <html>
9      <body>
10        {/* NEXT_PUBLIC_ - bezpiecznie używane w HTML */}
11        <h1>{process.env.NEXT_PUBLIC_APP_NAME}</h1>
12        {children}
13      </body>
14    </html>
15  );
16}
17
18// components/ApiStatus.tsx - Client Component
19'use client';
20import { useEffect, useState } from 'react';
21
22export default function ApiStatus() {
23  const [status, setStatus] = useState('checking...');
24
25  useEffect(() => {
26    // NEXT_PUBLIC_ zmienne działają w przeglądarce
27    const apiUrl = process.env.NEXT_PUBLIC_API_URL;
28
29    fetch(apiUrl + '/health')
30      .then(res => res.json())
31      .then(data => setStatus(data.status));
32  }, []);
33
34  return <span>API: {status}</span>;
35}

Runtime vs Build-time zmienne środowiskowe

Ważne jest zrozumienie, kiedy zmienne są odczytywane:

Build-time (czas budowania)

Zmienne

NEXT_PUBLIC_
są wstrzykiwane podczas
npm run build
. Oznacza to, że po zbudowaniu aplikacji ich wartości są "zamrożone" w kodzie JavaScript:

1# Te wartości zostaną wstrzyknięte do bundla podczas build:
2NEXT_PUBLIC_API_URL=https://api.quantum-city.com npm run build
3
4# Po zbudowaniu, zmiana tej zmiennej NIE zmieni zachowania aplikacji!
5# Trzeba przebudować (rebuild), aby zastosować nową wartość.

Runtime (czas wykonywania)

Zmienne serwerowe (bez

NEXT_PUBLIC_
) są odczytywane w czasie rzeczywistym — przy każdym żądaniu:

1// app/api/config/route.ts
2import { NextResponse } from 'next/server';
3
4export async function GET() {
5  // Ta wartość jest odczytywana PRZY KAŻDYM żądaniu
6  // Zmiana zmiennej na serwerze natychmiast wpływa na odpowiedź
7  const dbUrl = process.env.DATABASE_URL;
8  const maxConnections = process.env.DB_MAX_CONNECTIONS || '10';
9
10  return NextResponse.json({
11    database: dbUrl ? 'connected' : 'not configured',
12    maxConnections: parseInt(maxConnections),
13  });
14}

Walidacja zmiennych środowiskowych z Zod (wzorzec t3-env)

W profesjonalnych aplikacjach warto walidować zmienne środowiskowe przy starcie aplikacji, aby uniknąć błędów w runtime. Popularne podejście to użycie biblioteki Zod:

1// lib/env.ts
2import { z } from 'zod';
3
4// Schemat walidacji zmiennych serwerowych
5const serverEnvSchema = z.object({
6  DATABASE_URL: z.string().url('DATABASE_URL musi być poprawnym URL'),
7  JWT_SECRET: z.string().min(32, 'JWT_SECRET musi mieć min. 32 znaki'),
8  OPENAI_API_KEY: z.string().startsWith('sk-', 'Nieprawidłowy klucz OpenAI'),
9  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
10  PORT: z.coerce.number().default(4000),
11});
12
13// Schemat walidacji zmiennych klienckich
14const clientEnvSchema = z.object({
15  NEXT_PUBLIC_APP_NAME: z.string().min(1),
16  NEXT_PUBLIC_API_URL: z.string().url(),
17});
18
19// Walidacja przy imporcie modułu
20const serverEnv = serverEnvSchema.safeParse(process.env);
21const clientEnv = clientEnvSchema.safeParse(process.env);
22
23if (!serverEnv.success) {
24  console.error('Brakujące zmienne serwerowe:', serverEnv.error.format());
25  throw new Error('Nieprawidłowa konfiguracja środowiska serwerowego');
26}
27
28if (!clientEnv.success) {
29  console.error('Brakujące zmienne klienckie:', clientEnv.error.format());
30  throw new Error('Nieprawidłowa konfiguracja środowiska klienckiego');
31}
32
33export const env = {
34  ...serverEnv.data,
35  ...clientEnv.data,
36};

Użycie walidowanych zmiennych

1// app/api/users/route.ts
2import { env } from '@/lib/env';
3
4export async function GET() {
5  // TypeScript zna typy - env.DATABASE_URL jest zawsze string
6  // Walidacja gwarantuje, że wartość jest poprawnym URL
7  const response = await fetch(env.NEXT_PUBLIC_API_URL + '/users', {
8    headers: {
9      'Authorization': 'Bearer ' + env.JWT_SECRET,
10    },
11  });
12
13  return Response.json(await response.json());
14}

Konfiguracja next.config.js pod różne środowiska

Plik

next.config.js
może wykorzystywać zmienne środowiskowe do dynamicznej konfiguracji:

1// next.config.js
2/** @type {import('next').NextConfig} */
3const nextConfig = {
4  // Ustawianie dodatkowych zmiennych środowiskowych
5  env: {
6    CUSTOM_KEY: process.env.NODE_ENV === 'production'
7      ? 'production-value'
8      : 'development-value',
9    BUILD_TIME: new Date().toISOString(),
10  },
11
12  // Konfiguracja obrazów zależna od środowiska
13  images: {
14    remotePatterns: [
15      {
16        protocol: 'https',
17        hostname: process.env.NODE_ENV === 'production'
18          ? 'cdn.quantum-city.com'
19          : 'localhost',
20      },
21    ],
22  },
23
24  // Różne nagłówki dla dev i prod
25  async headers() {
26    const securityHeaders = [
27      { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
28      { key: 'X-Content-Type-Options', value: 'nosniff' },
29    ];
30
31    if (process.env.NODE_ENV === 'production') {
32      securityHeaders.push({
33        key: 'Strict-Transport-Security',
34        value: 'max-age=63072000; includeSubDomains; preload',
35      });
36    }
37
38    return [{ source: '/:path*', headers: securityHeaders }];
39  },
40};
41
42module.exports = nextConfig;

Zarządzanie sekretami w produkcji

W środowisku produkcyjnym nigdy nie przechowujemy sekretów w plikach

.env
. Zamiast tego używamy platform do zarządzania sekretami:

1# Vercel - dodawanie zmiennych przez CLI
2vercel env add DATABASE_URL production
3vercel env add JWT_SECRET production
4vercel env add OPENAI_API_KEY production
5
6# Vercel - podgląd zmiennych
7vercel env ls
8
9# Docker - zmienne przez docker-compose.yml
10# docker-compose.yml:
11# services:
12#   app:
13#     environment:
14#       - DATABASE_URL=${DATABASE_URL}
15#       - JWT_SECRET=${JWT_SECRET}
16
17# GitHub Actions - sekrety w CI/CD
18# Settings → Secrets and variables → Actions
19# Użycie: ${{ secrets.DATABASE_URL }}

Podsumowanie

Zmienne środowiskowe to fundament bezpiecznej konfiguracji aplikacji Next.js:

  1. Pliki .env — hierarchia:
    .env.local
    >
    .env.production
    >
    .env
  2. NEXT_PUBLIC_ prefix — tylko te zmienne trafiają do przeglądarki
  3. Runtime vs Build-time — zmienne serwerowe są dynamiczne, klienckie zamrożone po build
  4. Walidacja z Zod — gwarantuje poprawność konfiguracji
  5. Sekrety w produkcji — nigdy w plikach, zawsze przez platformę hostingową

Przećwicz te koncepcje w edytorze poniżej.

Ir a CodeWorlds