Kurs Next.js · Moduł 8: Deployment i produkcja

Preview Deployments i środowiska staging

6 min czytania
W tej lekcji6

W Metropolii Quantum 2150 żaden system nie trafia bezpośrednio na serwery produkcyjne. Każda zmiana przechodzi przez rygorystyczny proces weryfikacji - od preview deployments dla poszczególnych Pull Requestów, przez środowisko staging, aż po kontrolowaną promocję na produkcję. Taki pipeline zapewnia, że mieszkańcy Quantum City nigdy nie doświadczą wadliwego kodu.

Czym są Preview Deployments?

Preview Deployments to automatyczne wdrożenia aplikacji generowane dla każdego Pull Requesta. Każdy PR otrzymuje własny, unikalny URL, pod którym można przetestować zmiany przed ich zmergowaniem do głównej gałęzi.

Vercel - platforma stworzona przez twórców Next.js - oferuje najlepsze wsparcie dla Preview Deployments. Ale inne platformy, jak Netlify czy AWS Amplify, również wspierają ten koncept.

Jak działają Preview Deployments na Vercel?

  1. Tworzysz Pull Request na GitHubie
  2. Vercel automatycznie wykrywa nowy PR
  3. Buduje aplikację z kodem z tego PR
  4. Generuje unikalny URL (np. project-git-feature-xyz-team.vercel.app)
  5. Dodaje komentarz do PR z linkiem do preview
1# Przykładowe URL-e Preview Deployments:
2# PR #42: my-app-git-feature-login-myteam.vercel.app
3# PR #43: my-app-git-fix-header-myteam.vercel.app
4# PR #44: my-app-git-update-api-myteam.vercel.app
5
6# Każdy URL jest unikalny i działa dopóki PR istnieje

Konfiguracja Vercel dla Preview Deployments

1// vercel.json
2{
3  "git": {
4    "deploymentEnabled": {
5      "main": true,
6      "staging": true
7    }
8  },
9  "github": {
10    "autoAlias": true,
11    "autoJobCancelation": true,
12    "silent": false
13  },
14  "headers": [
15    {
16      "source": "/(.*)",
17      "headers": [
18        {
19          "key": "X-Robots-Tag",
20          "value": "noindex"
21        }
22      ]
23    }
24  ]
25}

Ważna uwaga: Preview Deployments powinny mieć nagłówek X-Robots-Tag: noindex, aby wyszukiwarki nie indeksowały wersji testowych!

Środowiska deployment (dev → staging → prod)

W profesjonalnych projektach stosuje się wielopoziomowy pipeline wdrożeń:

Hierarchia środowisk

1Development (dev)     → Lokalna maszyna developera
2    ↓
3Preview               → Automatyczne per-PR (Vercel Preview)
4    ↓
5Staging               → Odwzorowanie produkcji (testy końcowe)
6    ↓
7Production (prod)     → Serwery produkcyjne (użytkownicy końcowi)

Branch-based deployments

Każda gałąź Gita może być mapowana na inne środowisko:

1# Konfiguracja branch → environment w Vercel:
2# main        → Production  (app.quantum-city.com)
3# staging     → Staging     (staging.quantum-city.com)
4# develop     → Development (dev.quantum-city.com)
5# feature/*   → Preview     (auto-generated URL)
6
7# Workflow developera:
8git checkout -b feature/new-dashboard
9# ... kodowanie ...
10git push origin feature/new-dashboard
11# → Automatycznie: Preview Deployment!
12
13# Po review i merge do staging:
14git checkout staging
15git merge feature/new-dashboard
16git push origin staging
17# → Automatycznie: Staging Deployment!
18
19# Po testach na staging, merge do main:
20git checkout main
21git merge staging
22git push origin main
23# → Automatycznie: Production Deployment!

Zmienne środowiskowe per environment

Każde środowisko ma własne zmienne:

1// Vercel CLI - dodawanie zmiennych per środowisko
2// vercel env add DATABASE_URL development
3// vercel env add DATABASE_URL preview
4// vercel env add DATABASE_URL production
5
6// W kodzie sprawdzamy środowisko:
7// app/api/config/route.ts
8import { NextResponse } from 'next/server';
9
10export async function GET() {
11  const environment = process.env.VERCEL_ENV; // 'production' | 'preview' | 'development'
12  const gitBranch = process.env.VERCEL_GIT_COMMIT_REF; // np. 'feature/login'
13  const commitSha = process.env.VERCEL_GIT_COMMIT_SHA; // hash commitu
14
15  return NextResponse.json({
16    environment,
17    branch: gitBranch,
18    commit: commitSha?.slice(0, 7),
19    // Różne bazy danych per środowisko
20    database: environment === 'production'
21      ? 'prod-cluster'
22      : environment === 'preview'
23        ? 'preview-cluster'
24        : 'dev-cluster',
25  });
26}

Protected Deployments

W środowisku staging i preview warto chronić dostęp, aby niepowołane osoby nie widziały nieopublikowanych zmian:

1// middleware.ts - ochrona preview i staging
2import { NextRequest, NextResponse } from 'next/server';
3
4export function middleware(request: NextRequest) {
5  const environment = process.env.VERCEL_ENV;
6
7  // Nie chronij produkcji
8  if (environment === 'production') {
9    return NextResponse.next();
10  }
11
12  // Sprawdź autoryzację dla preview/staging
13  const authHeader = request.headers.get('authorization');
14  const previewToken = process.env.PREVIEW_ACCESS_TOKEN;
15
16  // Pozwól na dostęp z tokenem lub ciasteczkiem
17  const hasToken = authHeader === 'Bearer ' + previewToken;
18  const hasCookie = request.cookies.get('preview-access')?.value === previewToken;
19
20  if (!hasToken && !hasCookie) {
21    // Przekieruj na stronę logowania preview
22    return NextResponse.redirect(new URL('/preview-login', request.url));
23  }
24
25  return NextResponse.next();
26}
27
28export const config = {
29  matcher: ['/((?!preview-login|api|_next/static|favicon.ico).*)'],
30};

Deployment Hooks - automatyzacja wdrożeń

Deploy Hooks to URL-e webhooka, które wyzwalają nowe wdrożenie po wywołaniu:

1# Tworzenie Deploy Hook w Vercel:
2# Settings → Git → Deploy Hooks
3# Nazwa: "CMS Content Update"
4# Branch: main
5# → Generuje URL: https://api.vercel.com/v1/integrations/deploy/prj_xxx/yyy
6
7# Użycie z CMS (np. po publikacji artykułu):
8curl -X POST https://api.vercel.com/v1/integrations/deploy/prj_xxx/yyy
9
10# Użycie w GitHub Actions:
11# - name: Trigger Vercel Deploy
12#   run: curl -X POST ${{ secrets.VERCEL_DEPLOY_HOOK }}
13
14# Użycie z Strapi/Sanity webhook:
15# Po opublikowaniu treści → POST na Deploy Hook URL
16# → Vercel przebudowuje stronę z nowymi danymi (ISR/SSG)

Praktyczny workflow z Deploy Hooks

1// app/api/webhook/cms/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4export async function POST(request: NextRequest) {
5  // Weryfikacja webhooka z CMS
6  const signature = request.headers.get('x-webhook-signature');
7  const secret = process.env.CMS_WEBHOOK_SECRET;
8
9  if (!verifySignature(signature, secret)) {
10    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
11  }
12
13  // Wyzwól rewalidację lub nowy deploy
14  const deployHookUrl = process.env.VERCEL_DEPLOY_HOOK;
15
16  if (deployHookUrl) {
17    await fetch(deployHookUrl, { method: 'POST' });
18    return NextResponse.json({ status: 'Deploy triggered' });
19  }
20
21  return NextResponse.json({ status: 'No deploy hook configured' });
22}
23
24function verifySignature(signature: string | null, secret: string | undefined): boolean {
25  if (!signature || !secret) return false;
26  // Implementacja weryfikacji HMAC
27  return true;
28}

Environment Promotion (dev → staging → prod)

Promocja środowisk to kontrolowany proces przenoszenia kodu między środowiskami:

1# Strategia 1: Git-flow z automatycznym deploy
2# develop → auto-deploy na dev
3# staging → auto-deploy na staging
4# main    → auto-deploy na production
5
6# Merge workflow:
7git checkout staging
8git merge develop        # Promuj dev → staging
9git push origin staging  # Auto-deploy na staging
10
11# Po testach na staging:
12git checkout main
13git merge staging        # Promuj staging → prod
14git push origin main     # Auto-deploy na production
15
16# Strategia 2: Vercel Promote (bez merge)
17# Vercel pozwala promować istniejący deployment:
18vercel promote [deployment-url] --scope=team
19
20# To przenosi DOKŁADNIE ten sam build na produkcję
21# Bez ponownego budowania - gwarancja identyczności!

Podsumowanie

Preview Deployments i środowiska staging to fundamenty profesjonalnego procesu wdrożeń:

  1. Preview Deployments - automatyczne wdrożenia per PR, idealne do code review
  2. Branch-based deployments - mapowanie gałęzi na środowiska (dev/staging/prod)
  3. Environment promotion - kontrolowany przepływ kodu między środowiskami
  4. Protected Deployments - ochrona preview i staging przed nieautoryzowanym dostępem
  5. Deploy Hooks - automatyczne wyzwalanie wdrożeń z CMS lub CI/CD

W Metropolii Quantum 2150 żaden system nie przechodzi do produkcji bez pełnego cyklu preview → staging → production. To gwarantuje stabilność i jakość usług dla wszystkich mieszkańców cyberpunkowego miasta.

Kod do tej lekcji: App.tsx
1import React, { useState } from 'react';
2
3// Demo: Preview Deployments i srodowiska staging
4interface Deployment {
5  id: string;
6  branch: string;
7  env: 'production' | 'staging' | 'preview';
8  url: string;
9  status: 'ready' | 'building' | 'error';
10  pr?: number;
11  commit: string;
12  age: string;
13}
14
15const deployments: Deployment[] = [
16  { id: '1', branch: 'main', env: 'production', url: 'app.quantum-city.com', status: 'ready', commit: 'a1b2c3d', age: '2h ago' },
17  { id: '2', branch: 'staging', env: 'staging', url: 'staging.quantum-city.com', status: 'ready', commit: 'e4f5g6h', age: '1h ago' },
18  { id: '3', branch: 'feature/new-dashboard', env: 'preview', url: 'app-git-feature-new-dashboard-team.vercel.app', status: 'ready', pr: 42, commit: 'i7j8k9l', age: '30m ago' },
19  { id: '4', branch: 'fix/header-layout', env: 'preview', url: 'app-git-fix-header-layout-team.vercel.app', status: 'building', pr: 43, commit: 'm0n1o2p', age: '5m ago' },
20  { id: '5', branch: 'feature/auth-update', env: 'preview', url: 'app-git-feature-auth-team.vercel.app', status: 'error', pr: 44, commit: 'q3r4s5t', age: '15m ago' },
21];
22
23const envColors: Record<string, string> = { production: '#4caf50', staging: '#ff9800', preview: '#7c3aed' };
24const statusColors: Record<string, string> = { ready: '#4caf50', building: '#2196f3', error: '#f44336' };
25
26const pipeline = [
27  { name: 'Development', desc: 'Lokalna maszyna', icon: '1' },
28  { name: 'Preview', desc: 'Auto per PR', icon: '2' },
29  { name: 'Staging', desc: 'Testy koncowe', icon: '3' },
30  { name: 'Production', desc: 'Uzytkownicy', icon: '4' },
31];
32
33export default function PreviewDeploymentsDemo() {
34  const [filter, setFilter] = useState<'all' | 'production' | 'staging' | 'preview'>('all');
35  const [showPipeline, setShowPipeline] = useState(true);
36
37  const filtered = filter === 'all' ? deployments : deployments.filter(d => d.env === filter);
38
39  return (
40    <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
41      <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>Preview Deployments</h1>
42      <p style={{ color: '#8892b0', marginBottom: '24px' }}>Pipeline: dev -&gt; preview -&gt; staging -&gt; production</p>
43
44      {showPipeline && (
45        <div style={{ display: 'flex', gap: '4px', marginBottom: '24px', alignItems: 'center' }}>
46          {pipeline.map((step, i) => (
47            <React.Fragment key={step.name}>
48              <div style={{
49                flex: 1, padding: '12px', borderRadius: '8px', textAlign: 'center',
50                background: '#1a1a2e', border: '1px solid #333',
51              }}>
52                <div style={{ color: '#64ffda', fontWeight: 'bold', fontSize: '20px' }}>{step.icon}</div>
53                <div style={{ color: '#fff', fontSize: '13px', fontWeight: 'bold' }}>{step.name}</div>
54                <div style={{ color: '#8892b0', fontSize: '11px' }}>{step.desc}</div>
55              </div>
56              {i < pipeline.length - 1 && <span style={{ color: '#64ffda', fontSize: '20px' }}>&#8594;</span>}
57            </React.Fragment>
58          ))}
59        </div>
60      )}
61
62      <div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
63        {(['all', 'production', 'staging', 'preview'] as const).map(f => (
64          <button key={f} onClick={() => setFilter(f)} style={{
65            padding: '6px 12px', borderRadius: '6px', border: 'none', cursor: 'pointer', fontSize: '13px',
66            background: filter === f ? (f === 'all' ? '#64ffda' : envColors[f]) : '#1a1a2e',
67            color: filter === f ? '#0f0f23' : '#8892b0', fontWeight: 'bold',
68          }}>{f === 'all' ? 'Wszystkie' : f.charAt(0).toUpperCase() + f.slice(1)}</button>
69        ))}
70        <button onClick={() => setShowPipeline(!showPipeline)} style={{
71          padding: '6px 12px', borderRadius: '6px', border: '1px solid #333', background: 'transparent', color: '#8892b0', cursor: 'pointer', fontSize: '13px', marginLeft: 'auto',
72        }}>{showPipeline ? 'Ukryj pipeline' : 'Pokaz pipeline'}</button>
73      </div>
74
75      <div style={{ display: 'grid', gap: '8px' }}>
76        {filtered.map(d => (
77          <div key={d.id} style={{
78            padding: '14px', borderRadius: '8px', background: '#1a1a2e',
79            border: '1px solid ' + envColors[d.env] + '40',
80          }}>
81            <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' }}>
82              <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: statusColors[d.status], flexShrink: 0 }} />
83              <code style={{ color: '#fff', fontWeight: 'bold', fontSize: '13px' }}>{d.branch}</code>
84              <span style={{ fontSize: '10px', padding: '2px 8px', borderRadius: '4px', background: envColors[d.env] + '20', color: envColors[d.env] }}>{d.env}</span>
85              {d.pr && <span style={{ fontSize: '11px', color: '#8892b0' }}>PR #{d.pr}</span>}
86              <span style={{ fontSize: '11px', color: '#666', marginLeft: 'auto' }}>{d.age}</span>
87            </div>
88            <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
89              <code style={{ color: '#8892b0', fontSize: '12px' }}>{d.url}</code>
90              <code style={{ color: '#666', fontSize: '11px', marginLeft: 'auto' }}>{d.commit}</code>
91            </div>
92          </div>
93        ))}
94      </div>
95    </div>
96  );
97}

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Preview Deployments na Vercel są generowane automatycznie:

  2. 2. CDN (Content Delivery Network) zapewnia:

To 2 z 11 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Układanie w pionie

    Ułóż prawidłową kolejność środowisk w pipeline wdrożeń:

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: Define experiment → Split traffic → Trac

  • Układanie w pionie

    Ułóż kroki analizy rozmiaru bundle'a w prawidłowej kolejności:

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: Audit dependencies → Set CSP headers → E

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: Code commit do repository → CI/CD pipeli

  • Układanie w poziomie

    Ułóż konfigurację ESLint w pliku .eslintrc.json

  • Układanie w pionie

    Uporządkuj etapy obsługi incydentów produkcyjnych

  • Klikanie w kolejności

    Ułóż polecenie Docker build:

  • Edytor kodu

    Utwórz w Markdown checklistę wdrożenia produkcyjnego z pięcioma sekcjami (nagłówkami): bezpieczeństwo, wydajność, monitoring, kopie zapasowe i zgodność z przepisami. W każdej sekcji dodaj co najmniej dwa punkty jako pola wyboru (- [ ] ...).

  • Klikanie w kolejności

    Ułóż konfigurację Vercel deploy:

Przydatne artykuły