Next.js-Entwickler in Frankfurt

Next.js Entwickler für Unternehmen in Frankfurt am Main: Content Security Policy mit Nonce über proxy.ts, was der Nonce mit statischem Rendering macht, Sicherheitsheader in next.config, Drittskripte mit next/script und Tests im Report-Only-Modus. Remote und schriftlich, erste Einschätzung kostenlos.

Next.js-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

#Content Security Policy für Next.js-Anwendungen in Frankfurt

In Frankfurt laufen viele Webanwendungen nah an ihren Nutzern. Nach eigenen Angaben von DE-CIX erreichen Teilnehmer am Frankfurter Internetknoten über 1.000 Netzwerke, der Spitzenverkehr liegt bei mehr als 18 Terabit pro Sekunde, und angeschlossen werden kann man in über 30 Rechenzentren (DE-CIX Frankfurt). Wer hier eine Next.js-Anwendung betreibt, legt sie oft hinter ein CDN und lässt fertige Seiten am Rand des Netzes zwischenspeichern.

Genau an dieser Stelle kollidiert eine strenge Content Security Policy mit der Architektur. Eine Richtlinie mit Nonce verlangt bei jeder Anfrage einen neuen Wert, also eine frisch gerenderte Seite. Was vorher statisch aus dem Cache kam, wird zur Serverarbeit. Diese Seite erklärt, wie die CSP in Next.js technisch funktioniert, was sie kostet, welche Alternativen die Dokumentation anbietet und wie ich eine Richtlinie einführe, ohne dass am ersten Tag der Tag Manager oder das Einwilligungsbanner verschwindet.

Ich bin Mariusz Szatkowski, ein einzelner Entwickler aus Gdynia. Mit WordPress arbeite ich seit 2006, heute auch mit Next.js und Headless-Architekturen. Für Frankfurter Auftraggeber arbeite ich remote und schriftlich, ohne Vor-Ort-Termine. Gestaltung, Entwürfe und Wireframes liefern Sie, ich setze sie um. Die Prüfliste weiter unten können Sie auch ohne mich verwenden.

Alte Oper am Opernplatz in Frankfurt am Main

Foto: Thomas Wolf, www.foto-tw.de, CC BY-SA 3.0 de, Wikimedia Commons.

#Was eine Content Security Policy leistet

Eine CSP ist ein Antwortheader, mit dem die Anwendung dem Browser sagt, woher Skripte, Stylesheets, Bilder, Schriften und Frames geladen werden dürfen. Die Next.js-Anleitung zur CSP nennt als Ziel den Schutz vor Cross-Site-Scripting, Clickjacking und anderen eingeschleusten Inhalten. Gelingt es einem Angreifer, über ein Kommentarfeld oder einen kompromittierten Baustein ein Skript in die Seite zu bringen, verweigert der Browser die Ausführung, wenn die Richtlinie es nicht erlaubt.

Für Skripte empfiehlt der CSP-Leitfaden von MDN Nonces oder Hashes statt langer Domainlisten. Ein Nonce ist ein Zufallswert, den der Server in den Header und in jedes erlaubte <script>-Tag schreibt. Der Browser führt nur aus, was den passenden Wert trägt. Das funktioniert nur, wenn der Wert für jede Antwort neu und nicht vorhersehbar ist. Zwei Details aus dem Leitfaden übersehe ich in Durchsichten selten zweimal: Steht ein Nonce oder Hash in der Direktive, ignorieren Browser 'unsafe-inline'. 'unsafe-eval' dagegen wirkt weiter, auch neben einem Nonce.

#Der Nonce in proxy.ts

Seit Version 16.0.0 heißt die frühere Middleware in Next.js proxy.ts und läuft standardmäßig in der Node.js-Laufzeit (proxy.js-Referenz). Für ältere Projekte gibt es einen Codemod, der Datei und Funktion umbenennt: npx @next/codemod@canary middleware-to-proxy .. In dieser Datei entsteht der Nonce, bevor die Seite gerendert wird. Gekürzt auf das Wesentliche sieht das Beispiel aus der Dokumentation so aus:

// proxy.ts
import { NextRequest, NextResponse } from 'next/server'

export function proxy(request: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const isDev = process.env.NODE_ENV === 'development'
  const csp = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic'${isDev ? " 'unsafe-eval'" : ''};
    style-src 'self' 'nonce-${nonce}';
    img-src 'self' blob: data:;
    font-src 'self';
    object-src 'none';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
    upgrade-insecure-requests;
  `.replace(/\s{2,}/g, ' ').trim()

  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-nonce', nonce)
  requestHeaders.set('Content-Security-Policy', csp)

  const response = NextResponse.next({ request: { headers: requestHeaders } })
  response.headers.set('Content-Security-Policy', csp)
  return response
}

export const config = {
  matcher: [
    {
      source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
      missing: [
        { type: 'header', key: 'next-router-prefetch' },
        { type: 'header', key: 'purpose', value: 'prefetch' },
      ],
    },
  ],
}

Der Header wird zweimal gesetzt, und das ist Absicht. Die Kopie in der Anfrage liest Next.js beim Rendern: Es sucht das Muster 'nonce-...' und hängt den Wert selbst an Framework-Skripte, die Bundles der Seite und eigene Inline-Tags. Die Kopie in der Antwort liest der Browser. Der Matcher spart statische Dateien, Bildoptimierung und Prefetch-Anfragen aus, die keinen eigenen Header brauchen. 'unsafe-eval' steht nur in der Entwicklung, weil React dort eval für bessere Fehlermeldungen nutzt; in Produktion braucht weder React noch Next.js es.

Eigene Skripte bekommen den Wert über headers(). Im Root-Layout sieht das so aus:

// app/layout.tsx
import { headers } from 'next/headers'
import Script from 'next/script'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const nonce = (await headers()).get('x-nonce') ?? undefined
  return (
    <html lang="de">
      <body>
        {children}
        <Script src="/scripts/einwilligung.js" strategy="afterInteractive" nonce={nonce} />
      </body>
    </html>
  )
}

#Was der Nonce mit statischem Rendering macht

Hier liegt die eigentliche Entscheidung. Statische Seiten entstehen beim Build, und dort gibt es weder Anfrage noch Header, also auch keinen Nonce. Die Dokumentation zieht daraus klare Folgen: Mit Nonce müssen alle betroffenen Seiten dynamisch gerendert werden. Statische Optimierung und Incremental Static Regeneration entfallen, Partial Prerendering ist nicht vereinbar, weil die statische Hülle den Nonce nicht kennt, und ein CDN kann die Seiten ohne zusätzliche Einstellungen nicht zwischenspeichern. Jede Anfrage kostet Rechenzeit auf dem Server.

Ein tückischer Punkt steht ebenfalls dort: Der Build läuft durch, die Fehler zeigen sich erst zur Laufzeit. Wo Next.js eine Seite sonst statisch ausliefern würde, erzwinge ich das dynamische Rendering ausdrücklich mit await connection() aus next/server. Für eine Anwendung mit Kundenbereich ist der Preis meist tragbar, weil diese Seiten ohnehin personalisiert sind. Für eine Unternehmensseite mit hundert Inhaltsseiten, die bisher komplett aus dem Cache kam, ist er es oft nicht.

Die Dokumentation nennt deshalb zwei andere Wege:

VarianteWie sie arbeitetWann ich sie wähle
Nonce über proxy.tsNeuer Wert je Anfrage, Next.js setzt ihn an alle eigenen SkripteKundenbereiche, Formulare mit personenbezogenen Daten, strenge interne Vorgaben ohne 'unsafe-inline'
Header in next.config ohne NonceFeste Richtlinie, Skripte mit 'unsafe-inline' erlaubtInhaltsseiten, die statisch und am CDN gecacht bleiben sollen
Subresource Integrity (experimentell)Hashes der Skripte beim Build, integrity-Attribut an jedem TagStatische Seiten mit strenger Richtlinie, sofern der Betrieb ein experimentelles Merkmal akzeptiert

Die SRI-Variante wird mit experimental.sri und einem Algorithmus wie sha256 eingeschaltet, gibt es seit Version 14.0.0 und funktioniert nur im App Router. Für Skripte, die erst zur Laufzeit entstehen, hilft sie nicht. Die beiden Wege lassen sich kombinieren: statische Bereiche mit SRI, ein dynamischer Kundenbereich zusätzlich mit Nonce.

#strict-dynamic und warum Domainlisten nicht mehr greifen

Das Beispiel der Dokumentation enthält 'strict-dynamic'. Laut MDN zu script-src überträgt dieser Ausdruck das Vertrauen von einem Skript mit Nonce auf alle Skripte, die es nachlädt. Gleichzeitig ignorieren Browser mit CSP Level 3 Host-Listen und 'self' in derselben Direktive. Eine Domain in script-src erlaubt dann nichts mehr; entscheidend ist, ob das einbindende Skript den Nonce trägt.

In der Praxis heißt das: Ein Tag Manager, der seine Container selbst nachlädt, funktioniert mit strict-dynamic, solange sein erstes Tag den Nonce hat. Ein Skript, das jemand als festes <script>-Tag ohne Nonce ins Layout geschrieben hat, wird blockiert, auch wenn seine Domain in der Liste steht. Wo ältere Browser noch eine Rolle spielen, zeigt MDN eine abwärtskompatible Schreibweise mit 'unsafe-inline' https: neben Nonce und 'strict-dynamic'; neuere Browser ignorieren die ersten beiden Angaben.

#Drittskripte mit next/script

Die meisten Probleme nach Einführung einer CSP kommen von Drittanbietern: Einwilligungsbanner, Analyse, Chat, Kartendienste. Die Referenz zur Script-Komponente beschreibt vier Ladestrategien, und jede hat Folgen für die Richtlinie:

StrategieVerhalten laut DokumentationTypische Beispiele
beforeInteractiveSteht im HTML vom Server, landet immer im head, nur im Root-Layout erlaubtEinwilligungsverwaltung, Bot-Erkennung
afterInteractiveStandard, wird nach Teilen der Hydrierung im Browser eingefügtTag Manager, Analyse
lazyOnloadLädt in Leerlaufzeit, wenn alle Ressourcen da sindChat, Widgets sozialer Netzwerke
workerExperimentell, im App Router nicht nutzbarkeine

Für den Google Tag Manager zeigt die CSP-Anleitung die Komponente aus @next/third-parties/google mit einer nonce-Prop. Zusätzlich müssen connect-src und img-src die Domains enthalten, an die das Tag Daten sendet. Die Anleitung listet weitere häufige Verstöße: Inline-Styles aus CSS-in-JS-Bibliotheken ohne Nonce-Unterstützung, WebAssembly, das 'wasm-unsafe-eval' braucht, und Service Worker.

Jeder zusätzliche Anbieter in der Richtlinie ist eine Entscheidung, die jemand im Unternehmen verantworten sollte. Ich lege dafür eine Tabelle an: Anbieter, Zweck, Ladestrategie, benötigte Direktiven, Einwilligung ja oder nein.

#Weitere Sicherheitsheader in next.config

Neben der CSP gehören einige feste Header in jede Anwendung. Die Referenz zu headers nennt sie mit Beispielwerten. Diese Konfiguration setze ich als Ausgangspunkt und passe sie an:

// next.config.js
const sicherheitsHeader = [
  { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
  { key: 'X-Content-Type-Options', value: 'nosniff' },
  { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
  { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=(), browsing-topics=()' },
]

module.exports = {
  async headers() {
    return [{ source: '/(.*)', headers: sicherheitsHeader }]
  },
}

Drei Regeln aus der Referenz verhindern Überraschungen. Passen zwei Einträge auf denselben Pfad und setzen denselben Header, gewinnt der letzte. Die Header werden vor dem Dateisystem geprüft, gelten also auch für Dateien aus public. Und für unveränderliche Dateien mit Hash im Namen setzt Next.js Cache-Control selbst, das lässt sich in next.config nicht überschreiben.

Bei Strict-Transport-Security mit includeSubDomains und preload frage ich vorher nach, ob noch eine Subdomain nur über HTTP erreichbar ist, etwa ein altes Intranet oder ein Testsystem. Der Header schneidet sie laut Dokumentation für zwei Jahre ab. X-Frame-Options ist durch frame-ancestors in der CSP abgelöst, die Dokumentation zeigt ihn nur noch der Vollständigkeit halber.

#Testen mit Report-Only und den Entwicklerwerkzeugen

Eine neue Richtlinie schalte ich nie direkt scharf. Der Header Content-Security-Policy-Report-Only meldet Verstöße, ohne etwas zu blockieren. Damit Berichte ankommen, muss die Richtlinie über report-to einen Endpunkt nennen, der im Header Reporting-Endpoints definiert ist. Das ältere report-uri ist veraltet; MDN rät, beide anzugeben, solange report-to nicht in allen Browsern läuft. Report-Only lässt sich nicht über ein <meta>-Element setzen, nur als echter Antwortheader.

Laut MDN beachtet der Browser beide Header gleichzeitig: die durchgesetzte Richtlinie und die beobachtete. So teste ich eine verschärfte Fassung neben der laufenden. Mein Ablauf:

  1. Bestandsaufnahme: alle Skripte, Styles, Schriften, Frames und Verbindungen, die die Anwendung heute lädt.
  2. Eine Report-Only-Richtlinie aus next.config auf der Testumgebung, dann auf Produktion, mit einem eigenen Endpunkt für die Berichte.
  3. Durchsicht der Berichte und der Ansicht im Browser. Chrome listet CSP-Verstöße im Issues-Panel der DevTools übersichtlicher als in der Konsole.
  4. Erst wenn die Berichte nur noch erwartete Einträge zeigen, wird die Richtlinie durchgesetzt, bei Bedarf mit Nonce über proxy.ts.
  5. Der Bericht-Endpunkt bleibt aktiv, damit neue Verstöße nach einem Update auffallen.

#Recht und Datenschutzaufsicht in Hessen

Eine CSP ist keine gesetzliche Pflicht mit eigenem Paragrafen. Art. 32 DSGVO verlangt aber technische und organisatorische Maßnahmen, die den Stand der Technik berücksichtigen, und unter Buchstabe d ein Verfahren zur regelmäßigen Überprüfung ihrer Wirksamkeit. Ein Bericht-Endpunkt, der Verstöße sammelt, ist genau so ein Prüfverfahren für die Auslieferung von Skripten. Rechtsberatung gebe ich nicht; die Einordnung für Ihr Unternehmen trifft Ihre Datenschutzberatung.

Zuständig für Unternehmen in Frankfurt ist der Hessische Beauftragte für Datenschutz und Informationsfreiheit (HBDI) in Wiesbaden. In seiner Übersicht zu Datenschutzverletzungen nennt er die Fehlkonfiguration einer Webanwendung ausdrücklich als mögliche Ursache. Ein zweiter Hinweis des HBDI berührt die Richtlinie direkt: Zu Google Fonts erklärt die Behörde, dass beim Laden der Schriften personenbezogene Daten an Google übermittelt werden, und empfiehlt, Schriften lokal auf dem eigenen Server bereitzustellen; das gelte auch für andere Anbieter. Die Direktive font-src 'self' setzt genau das technisch durch. Wie next/font Schriften lokal ausliefert, beschreibe ich auf der Seite für Düsseldorf.

#Prüfliste für die CSP einer Next.js-Anwendung

  • Die Richtlinie enthält object-src 'none', base-uri 'self', form-action 'self' und frame-ancestors.
  • In Produktion steht kein 'unsafe-eval' in script-src, außer ein Anbieter oder WebAssembly verlangt es nachweislich.
  • Seiten mit Nonce sind ausdrücklich dynamisch, und das Team weiß, welche Routen dadurch nicht mehr aus dem Cache kommen.
  • Jedes Drittskript läuft über next/script oder @next/third-parties mit nonce-Prop, nicht als festes Tag im Layout.
  • connect-src und img-src enthalten nur die Domains, die die eingebundenen Dienste wirklich nutzen.
  • Schriften kommen vom eigenen Server.
  • Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy und Permissions-Policy sind gesetzt.
  • Ein Report-Only-Header mit report-to und Reporting-Endpoints lief vor dem Scharfschalten auf Produktion.
  • Die Tabelle der Drittanbieter ist aktuell und hat eine verantwortliche Person.
  • Next.js ist aktuell; laut npm-Registry ist 16.4.6 vom 9. November 2026 die neueste Version.

#So arbeite ich mit Frankfurter Auftraggebern

  1. Sie schicken mir die Adresse der Anwendung und beschreiben das Ziel: neue Richtlinie, Verschärfung einer bestehenden oder eine Durchsicht.
  2. Ich rufe die Antwortheader selbst ab, stelle Rückfragen schriftlich und schicke einen Umfang mit Annahmen und Ausschlüssen.
  3. Ich erstelle die Bestandsaufnahme der geladenen Ressourcen und die Tabelle der Drittanbieter.
  4. Die Richtlinie läuft zuerst im Report-Only-Modus auf Ihrer Testumgebung; Sie prüfen und nehmen ab.
  5. Nach der Freigabe schalte ich sie auf Produktion scharf. Die Berichte können Sie selbst auswerten, oder ich sehe sie nach Absprache regelmäßig durch.

Für den Start brauche ich Lesezugriff auf das Repository, Zugang zur Testumgebung und den Namen der Person, die über neue Drittanbieter entscheidet. Entsteht ein neues Frontend, liefern Sie das Layout, zum Beispiel als Tabelle, in der Sie je Ansicht die Elemente eintragen.

#Weitere Seiten für Frankfurt und Next.js

#Häufige Fragen zu CSP und Sicherheitsheadern in Next.js

#Macht ein Nonce meine Next.js-Seiten langsamer?

Er ändert, wie sie entstehen. Eine Seite mit Nonce muss bei jeder Anfrage auf dem Server gerendert werden, weil der Wert jedes Mal neu ist. Laut Next.js-Dokumentation entfallen damit statische Optimierung und ISR, ein CDN kann die Seite ohne zusätzliche Einstellungen nicht zwischenspeichern, und der Server trägt mehr Last. Ob das für Ihre Anwendung tragbar ist, kläre ich vor dem Umbau Route für Route.

#Reicht eine CSP in next.config ohne Nonce?

Für viele Seiten ja, mit einer klaren Grenze. Die Variante ohne Nonce erlaubt Inline-Skripte mit 'unsafe-inline' und schützt deshalb weniger gegen eingeschleuste Skripte. Dafür bleiben Seiten statisch und lassen sich am CDN zwischenspeichern. Eine dritte Möglichkeit ist die experimentelle Subresource Integrity, die Hashes beim Build erzeugt.

#Warum lädt mein Google Tag Manager nach Einführung der CSP nicht mehr?

Meist fehlt der Nonce am Skript. Mit strict-dynamic zählt in modernen Browsern nicht die Domain in script-src, sondern ob das einbindende Skript den Nonce trägt. Die Komponente aus @next/third-parties nimmt den Nonce als Prop. Zusätzlich brauchen connect-src und img-src die Domains, an die das Tag Daten sendet.

#Wie teste ich eine CSP, ohne die Seite für Besucher zu brechen?

Mit dem Header Content-Security-Policy-Report-Only. Der Browser meldet Verstöße, blockiert aber nichts. Damit Berichte ankommen, braucht die Richtlinie ein Ziel über report-to und einen passenden Reporting-Endpoints-Header. Im Chrome sehen Sie die Verstöße zusätzlich im Issues-Panel der Entwicklerwerkzeuge.

#Brauche ich X-Frame-Options noch?

Die Next.js-Dokumentation nennt den Header durch die CSP-Direktive frame-ancestors abgelöst, die in modernen Browsern besser unterstützt wird. Ich setze frame-ancestors in jeder Richtlinie. X-Frame-Options lasse ich daneben stehen, wenn er schon vorhanden ist; er stört die neuere Direktive nicht.

#Welche Angaben brauchen Sie für eine erste Einschätzung der CSP?

Die Adresse der Anwendung, die Next.js-Version aus der package.json, den Ort des Betriebs (Vercel, eigener Server, Container), eine Liste der eingebundenen Drittanbieter wie Tag Manager, Chat oder Einwilligung und die Antwortheader, die Ihre Seite heute ausliefert. Die Header kann ich von außen selbst abrufen.

#Ihre Anwendung in Frankfurt absichern

Vielleicht hat Ihre interne Revision eine CSP ohne 'unsafe-inline' verlangt, vielleicht ist nach einem Update der Tag Manager verschwunden, oder Sie möchten einfach wissen, welche Header Ihre Anwendung heute ausliefert. Schreiben Sie mir über das Kontaktformular und nennen Sie die Adresse, Ihr Ziel, einen Wunschtermin und den Stack, auf dem die Anwendung läuft. Ich antworte an Werktagen schriftlich. Die erste Einschätzung kostet Sie nichts.

Brauchen Sie die Leistung: Next.js-Entwickler in Frankfurt?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken