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ą.
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ć:
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: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 środowiskKaż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.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=productionTo 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ędzie1// 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}Ważne jest zrozumienie, kiedy zmienne są odczytywane:
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ść.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}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};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}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;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 }}Zmienne środowiskowe to fundament bezpiecznej konfiguracji aplikacji Next.js:
.env.local > .env.production > .envPrzećwicz te koncepcje w edytorze poniżej.