Wypuszczenie aplikacji Next.js zrobionej w Cursorze, v0 czy Bolt jest szybkie, ale szybkość ukrywa luki, których nie widzisz, dopóki ktoś nie znajdzie ich za Ciebie. Ta checklista przechodzi przez to, co trzeba sprawdzić przed wciśnięciem deploy, z wyjaśnieniem, dlaczego każdy punkt ma znaczenie, jak go sprawdzić i jakim kodem go naprawić. Na końcu jest wersja dwuminutowa.
Jak Next.js rysuje granicę między serwerem a klientem
Prawie każda luka bezpieczeństwa w Next.js to ta sama luka: coś, co powinno zostać na serwerze, przeszło do przeglądarki. Server Components wykonują się na serwerze i nigdy nie wysyłają swojego kodu do klienta; gdy tylko plik ma 'use client', wszystko, co importuje, ląduje w publicznym bundle'u. Route Handlery (app/api/*/route.ts) i Server Actions to publiczne adresy, nawet jeśli ich nie widzisz. Middleware działa przed wszystkim, ale nie jest murem. Gdy ta granica jest jasna, reszta listy to mechanika.
1. Zmienne środowiskowe: uwaga na NEXT_PUBLIC
Dlaczego to ważne. Wszystko, co zaczyna się od NEXT_PUBLIC_, jest wbudowywane w JavaScript przeglądarki w momencie builda. Każdy może to odczytać narzędziami deweloperskimi. Nigdy nie wkładaj tam prywatnego klucza API, tokena serwisowego ani sekretu bazy danych.
Jak sprawdzić. Otwórz swój .env i panel Vercela i przeczytaj każdą zmienną z publicznym prefiksem, pytając: czy coś by się stało, gdybym opublikował ją na Twitterze? Jeśli tak, usuń prefiks i przenieś ją na serwer.
Jak naprawić. Zmienne bez prefiksu istnieją tylko na serwerze: czytaj je z Server Components, Route Handlerów lub Server Actions. Żeby moduł z sekretami nigdy nie mógł zostać zaimportowany z komponentu klienta, dodaj import 'server-only' w pierwszej linii: jeśli ktoś zaimportuje go z niewłaściwej strony, build się wywali zamiast wyciekać klucz. Więcej w sekretach, które Twoja aplikacja wycieka w przeglądarce.
2. Route Handlery i Server Actions: zawsze waliduj i autoryzuj
Dlaczego to ważne. Route API nie jest prywatny dlatego, że leży w Twoim folderze. To publiczny adres, który każdy może wywołać przez curl. I Server Action, nawet wywoływana z przycisku, też: Next.js wystawia ją jako endpoint POST z identyfikatorem. To, że przycisk jest ukryty przed użytkownikiem, nie powstrzyma nikogo przed uderzeniem w endpoint bezpośrednio.
Jak naprawić. Sprawdzaj sesję i uprawnienia wewnątrz każdego handlera i każdej akcji, nie tylko w interfejsie. Waliduj ciało żądania schematem (zod lub podobnym), zanim dotkniesz bazy danych. Nigdy nie przyjmuj identyfikatora użytkownika od klienta: bierz go z sesji. I nałóż limit żądań na to, co kosztuje pieniądze albo wysyła e-maile.
// app/actions/update-profile.ts
'use server'
import { z } from 'zod'
import { getSession } from '@/lib/auth'
const Schema = z.object({ name: z.string().min(1).max(80) })
export async function updateProfile(input: unknown) {
const session = await getSession()
if (!session) throw new Error('Unauthorized')
const data = Schema.parse(input)
// użyj session.user.id, nigdy id przysłanego przez klienta
await db.user.update({ where: { id: session.user.id }, data })
}
3. Middleware to nie jedyne uwierzytelnianie
Dlaczego to ważne. Kusi, żeby chronić cały /dashboard z middleware.ts i o tym zapomnieć. W marcu 2025 opublikowano CVE-2025-29927: wewnętrzny nagłówek (x-middleware-subrequest) pozwalał całkowicie ominąć middleware w dotkniętych wersjach, a wraz z nim każde sprawdzanie sesji, które żyło tylko tam. Poprawiono to w Next.js 15.2.3 i 14.2.25 (z łatkami dla 13 i 12), ale lekcja zostaje.
Jak naprawić. Zaktualizuj Next.js do załatanej wersji i używaj middleware jako pierwszej warstwy (przekierowanie tych bez sesji), nigdy jako jedynej. Sprawdzenie, które naprawdę chroni dane, idzie do strony, handlera lub akcji, która je serwuje.
4. Nagłówki bezpieczeństwa
Dlaczego to ważne. Domyślnie Next.js nie wysyła nagłówków obronnych. Bez nich Twoja aplikacja jest wystawiona na clickjacking, na wykonywanie przez przeglądarkę jako skrypt czegoś, co nim nie jest, i na downgrade do HTTP.
Jak naprawić. W next.config.js:
// next.config.js
const securityHeaders = [
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
]
module.exports = {
poweredByHeader: false,
productionBrowserSourceMaps: false,
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }]
},
}
Content-Security-Policy zasługuje na osobne potraktowanie: Next.js wstrzykuje skrypty inline do hydratacji strony, więc rygorystyczna CSP potrzebuje nonce generowanego w middleware i przekazywanego do komponentów. Zacznij w trybie Report-Only. Szczegóły każdego nagłówka są w nagłówkach bezpieczeństwa HTTP wyjaśnionych po ludzku.
5. Sekrety przechodzące do Client Components
Dlaczego to ważne. Prywatny klucz łatwo ląduje w komponencie klienta przez pomyłkę, zwłaszcza gdy AI przepisuje pliki i przenosi importy. Jeśli pojawi się w serwowanym JavaScripcie, jest już skompromitowany.
Jak sprawdzić. Po next build poszukaj w .next/static ciągów takich jak sk_live, service_role, sk-ant czy adresów webhooków. Jeśli się pojawią, zrotuj je i przenieś na serwer z import 'server-only' jako zabezpieczeniem.
6. Zdalne obrazy i żądania do adresów od użytkownika
Dlaczego to ważne. next/image optymalizuje obrazy przez Twój serwer. Jeśli w images.remotePatterns pozwolisz na dowolny host, każdy może używać Twojego serwera jako proxy obrazów i zużywać Twój limit. To samo z route'em API, który pobiera adres podany przez użytkownika: może wskazywać na wewnętrzne usługi Twojej infrastruktury (SSRF).
Jak naprawić. Ogranicz remotePatterns do domen, których naprawdę używasz. Jeśli route musi pobierać zewnętrzne adresy, waliduj protokół (tylko https), odrzucaj prywatne IP i wewnętrzne nazwy, i ustaw limit czasu.
7. Otwarte przekierowania
Dlaczego to ważne. Klasyczne redirect(searchParams.next) po logowaniu pozwala budować linki w Twojej domenie, które kończą się na stronie phishingowej: twojadomena.pl/login?next=https://zly.example.
Jak naprawić. Przyjmuj tylko ścieżki względne zaczynające się od / (a nie od //), albo porównuj z listą dozwolonych celów.
8. Pliki, które nie powinny być dostępne
Dlaczego to ważne. Wszystko w public/ jest serwowane jak leci: .env skopiowany tam przez pomyłkę, zrzut bazy danych albo service-account.json są o jeden URL od wycieku. A source mapy na produkcji pozwalają czytać Twój kod klienta z oryginalnymi nazwami.
Jak sprawdzić. Spróbuj twojadomena.pl/.env, /.git/HEAD i otwórz plik JS z bundle'a, żeby zobaczyć, czy kończy się linią sourceMappingURL wskazującą na dostępny .map. Spójrz na folder public/ oczami obcego.
9. Ciasteczka sesyjne i biblioteki uwierzytelniania
Dlaczego to ważne. Auth.js (NextAuth), Clerk czy Supabase ze swoim pakietem serwerowym domyślnie dobrze konfigurują ciasteczka. Zawodzi to, co dodajesz sam: własne ciasteczko bez HttpOnly, słaby albo skopiowany z tutorialu AUTH_SECRET, albo sesja trzymana w localStorage.
Jak naprawić. Wygeneruj sekret przez openssl rand -base64 32, trzymaj go bez publicznego prefiksu, a dla każdego własnego ciasteczka używaj Secure, HttpOnly i SameSite=Lax.
10. Zależności i wersja Next.js
Dlaczego to ważne. Twoja aplikacja ciągnie setki pakietów, a sam Next.js publikuje poprawki bezpieczeństwa kilka razy w roku. Sprawa middleware z 2025 uderzyła w tych, którzy nie aktualizowali.
Jak naprawić. npm audit przed każdym wdrożeniem, Dependabot lub Renovate w repozytorium, i prosta zasada: aktualizować Next.js w tym samym miesiącu, w którym wychodzi łatka bezpieczeństwa.
Wdrożenie na Vercelu: trzy szczegóły
- Środowiska. Zmienne mają zakres (Production, Preview, Development). Nie dawaj wdrożeniom preview poświadczeń produkcyjnych: preview to publiczny adres z Twoim najnowszym kodem.
- Ochrona preview. Włącz Deployment Protection, żeby adresy preview wymagały logowania do Vercela; inaczej każdy z linkiem widzi branch w trakcie pracy.
- Nagłówki. Te z
next.config.jsdziałają na Vercelu bez niczego więcej; jeśli używasz teżvercel.json, nie dubluj ich.
Twoja dwuminutowa checklista
- Żadna zmienna
NEXT_PUBLIC_nie zawiera sekretu; moduły z kluczami mająimport 'server-only'. - Każdy Route Handler i Server Action sprawdza sesję i waliduje wejście; id użytkownika pochodzi z sesji.
- Next.js w załatanej wersji; middleware nie jest jedyną warstwą auth.
- Nagłówki bezpieczeństwa w
next.config.js;poweredByHeader: false; CSP przynajmniej w trybie raportowania. - Bundle bez
sk_live,service_roleani webhooków; source mapy wyłączone. remotePatternsograniczone; nic nie pobiera adresów od użytkownika bez walidacji.- Przekierowania tylko do ścieżek względnych.
public/bez wrażliwych plików;.envi.gitniedostępne.- Ciasteczka z Secure, HttpOnly i SameSite; losowy
AUTH_SECRET. npm auditczysty; preview chronione i bez sekretów produkcyjnych.
Sprawdź to wszystko w 30 sekund
Przechodzenie tej listy ręcznie przy każdym wdrożeniu jest nużące i się o tym zapomina. Scanaris wykonuje kontrole tylko do odczytu na Twojej opublikowanej stronie: nagłówki, sekrety wystawione w JavaScripcie, dostępne wrażliwe pliki, source mapy, ciasteczka i CORS. Każde znalezisko przychodzi z promptem gotowym do wklejenia w Cursora lub v0 z dokładną poprawką dla Next.js. Jeśli zostawisz monitoring, ostrzeże Cię, gdy sesja vibe codingu znów coś otworzy. Żeby zgłębić swój stack, jest strona bezpieczeństwa dla Next.js i ogólna checklista startowa.
Najczęściej zadawane pytania
Czy Server Actions są domyślnie bezpieczne?
Są wygodniejsze od route'a API, nie bezpieczniejsze. Next.js wystawia je jako publiczne endpointy; uwierzytelnianie i walidację wkładasz Ty do każdej z nich.
Czy middleware chroni moje route'y?
Służy do przekierowań i tanich sprawdzeń, ale nie może być jedyną barierą. Po sprawie z 2025 zasada brzmi: sprawdzać sesję także tam, gdzie serwowane są dane.
Czy potrzebuję CSP, używając Next.js?
Tak, ale z nonce i zaczynając w trybie raportowania. Bez nonce rygorystyczna CSP blokuje skrypty inline samego frameworka i strona zostaje biała.