Next.js selbst hosten - und was dabei aufhört zu funktionieren
Next.js läuft überall, wo Node läuft. Vier Funktionen liefert die Plattform statt des Frameworks, und zu wissen welche vier trennt eine ruhige Migration von einer überraschten.
"Können wir das auf eigener Infrastruktur betreiben?" hat eine kurze und eine
lange Antwort. Die kurze lautet ja: next start ist ein Node-Server und läuft
auf einer VM, in einem Container, hinter Ihrem Load Balancer, in Ihrer Region.
Die lange lautet, dass vier Dinge, die man für Next.js-Funktionen hält, tatsächlich von der Hosting-Plattform kommen - und genau diese vier überraschen Teams eine Woche nach der Migration.
Der Build, der wirklich deploybar ist
Der Standard-Build hinterlässt ein .next-Verzeichnis, das den kompletten
node_modules-Baum daneben braucht. Für einen Container sind das Hunderte
Megabyte Abhängigkeiten, die Sie nicht ausführen.
// next.config.ts
export default {
output: 'standalone',
};Das verfolgt die Module, die der Server tatsächlich erreicht, kopiert sie nach
.next/standalone und legt eine server.js dazu, die ohne Paketmanager
startet. In den Codebasen, die wir umgezogen haben, ging das Image von ~1,2 GB
auf ~180 MB.
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM node:22-alpine AS run
WORKDIR /app
ENV NODE_ENV=production
# Statische Assets und public/ liegen NICHT in standalone. Sie zu kopieren ist
# der Schritt, den man übersieht - das Symptom ist eine Seite ohne CSS.
COPY --from=build /app/.next/standalone ./
COPY --from=build /app/.next/static ./.next/static
COPY --from=build /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]Zwei Dinge an dieser Datei gehören klar gesagt. .next/static und public/
sind nicht Teil der Standalone-Ausgabe und müssen separat kopiert werden -
fehlt das, liefert das Deployment HTML ohne CSS und ohne Bilder. Und
npm run build läuft zur Image-Bauzeit, also muss jede Umgebungsvariable, die
Ihr Code auf Modulebene liest, dann vorhanden sein und nicht erst zur
Laufzeit.
Die vier Dinge, die die Plattform erledigt hat
Bildoptimierung. next/image braucht etwas, das skaliert und neu
kodiert. Auf einer Plattform ist das ein Dienst; auf Ihrem Server ist es
sharp, in Ihrem Prozess, auf Ihrer CPU.
npm install sharpInstalliert wird es automatisch benutzt. Wenn Ihr Traffic diese CPU-Kosten
real macht, richten Sie images.loader auf ein CDN, das skaliert, oder
erzeugen Sie die Größen zur Bauzeit vor. Was Sie nicht tun dürfen, ist
images.unoptimized: true in der Konfiguration stehen zu lassen und zu
vergessen - das schickt die Originaldatei an jeden Besucher und hebt still
den Grund auf, aus dem Sie die Komponente benutzt haben.
ISR und der Revalidierungs-Cache. Auf einem einzelnen Container
funktioniert das: Der Cache liegt auf der lokalen Platte. Auf drei Containern
hinter einem Load Balancer nicht - jeder hat seine eigene Platte,
revalidateTag leert den Cache auf der Instanz, die den Aufruf bekommen hat,
und die anderen beiden liefern die alte Seite weiter, bis sie zufällig selbst
revalidieren. Die Lösung ist ein gemeinsamer Cache-Handler:
// next.config.ts
export default {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // In-Process-LRU aus; Redis ist die Quelle
};Jeder Redis-gestützte Handler tut es. Wichtig ist zu verstehen, warum er nötig ist, denn "die Seite hat sich für manche Nutzer aktualisiert und für andere nicht" sieht sonst nach einem unmöglichen Fehler aus. Welcher Cache was tut, lohnt sich nachzulesen: die vier Ebenen und welche Sie erwischt hat.
Middleware an der Edge. Ihre Middleware läuft nicht mehr an hundert Orten, bevor die Anfrage einen Server erreicht; sie läuft in Ihrem Server, in Ihrer einen Region. Code, der in der Annahme geschrieben wurde, das sei fast kostenlos - ein Geo-Lookup, eine A/B-Zuweisung -, liegt jetzt im kritischen Pfad jeder Anfrage. Das sind echte Kosten, und sie gehören gemessen statt angenommen.
Streaming durch Ihren Proxy. Suspense und loading.tsx streamen die
Antwort in Stücken. Ein nginx davor mit Standardeinstellungen puffert die
ganze Antwort, bevor er sie sendet - damit wird Streaming zu einer langsameren
Variante von Nicht-Streaming, ohne dass eine Fehlermeldung es Ihnen sagt.
location / {
proxy_pass http://app:3000;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Connection "";
}Was Sie gewinnen
Nicht nur Kosten, obwohl der Unterschied in der Größe erheblich ist. Sie bekommen Ihre Daten in einer Jurisdiktion, die Sie gewählt haben - für einen Kunden mit türkischer oder EU-Anforderung an die Datenhaltung nicht verhandelbar. Sie können im selben Netz wie Ihre Datenbank laufen, was jedem Query einen Roundtrip spart. Und Sie bekommen ein Deployment, das niemand durch eine fremde Preisentscheidung unter Ihnen verändert.
Was es kostet
Jetzt gehört die Verfügbarkeit jemandem. Preview-Deployments pro Pull Request, sofortige Rollbacks und ein globales CDN sind echte Arbeit im Nachbau, und ein Team, das nie Infrastruktur betrieben hat, verbringt den ersten Monat damit, wiederzuentdecken, warum es diese Funktionen gibt.
Unsere Faustregel: Läuft die Anwendung in einer Region, hat das Team schon Dienste im Betrieb, oder gibt es eine Anforderung an die Datenhaltung, ist Selbst-Hosting schlicht richtig. Ist es eine Marketing-Seite mit zwei Entwicklern und globalem Traffic, ist die Plattform billiger als das Gehalt der Person, die die Alternative pflegt.
So oder so: Treffen Sie die Entscheidung auf dieser Grundlage und nicht anhand eines Benchmarks. Das Framework läuft an beiden Orten gleich - es sind die vier Funktionen oben, die sich verschieben.
