Next.js-Entwickler in Münster

Next.js Entwickler für Unternehmen in Münster: Uploads über Server Actions und Route Handler, Größenlimits laut Dokumentation, direkter Upload in den Objektspeicher, Virenprüfung und PDF-Erzeugung. 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

#Next.js in Münster: Dokumente, die Nutzer hochladen

Nach der Stadt Münster sind die Universität und das Universitätsklinikum die größten Arbeitgeber der Stadt. In der Standortentwicklungsstrategie 2030+ nennt sie unter anderem Gesundheitswirtschaft und Handwerk als Schwerpunkte (Stadt Münster, Wirtschaft und Wissenschaft). In beiden Bereichen wandern Dokumente durch Webanwendungen: Befunde und Einverständniserklärungen, Aufmaße und Fotos von der Baustelle, Bewerbungen, Nachweise für einen Antrag.

Ein Upload sieht im Formular harmlos aus und ist doch einer der heikelsten Punkte einer Anwendung. Er nimmt Bytes von Fremden an, schreibt sie irgendwohin und liefert sie später wieder aus. Auf dieser Seite gehe ich den Weg einer Datei durch eine Next.js-Anwendung: Annahme, Größenlimits, Ablage, Prüfung, PDF-Erzeugung und Auslieferung. Die Fakten stammen aus den Dokumentationen von Next.js, Cloudflare, AWS, ClamAV, Playwright und MDN, die ich im November 2026 gelesen habe.

Ich bin Mariusz Szatkowski, ein einzelner Entwickler in Gdynia, und arbeite seit 2006 mit WordPress, heute auch mit Next.js. Für Auftraggeber in Münster arbeite ich remote und schriftlich. Gestaltung und Wireframes liefern Sie, ich setze sie um.

Buddenturm in Münster mit Rasenfläche und Herbstbäumen

Foto: Dietmar Rabich, CC BY-SA 4.0, Wikimedia Commons.

#Upload über eine Server Action: die 1-MB-Grenze

Die Referenz zu serverActions nennt eine Standardgrenze von 1 MB für den Request-Body einer Server Action. Sie soll verhindern, dass das Parsen großer Datenmengen zu viele Serverressourcen verbraucht. Änderbar ist sie mit bodySizeLimit, als Zahl in Bytes oder als Text wie '3mb'.

// next.config.js
module.exports = {
  experimental: {
    serverActions: {
      bodySizeLimit: '4mb',
    },
  },
}

Wichtig ist ein Satz aus derselben Seite: Das Limit gilt für den rohen HTTP-Body, also einschließlich der Bytes, die multipart/form-data für Trennzeilen und Feldangaben hinzufügt. Für Uploads nahe am Limit empfiehlt die Dokumentation 10 bis 20 KB Reserve. Ein Foto von 4 MB passt deshalb nicht in ein Limit von 4 MB.

Wird das Limit überschritten, antwortet der Server mit 413, bei MDN Content Too Large. Mein Code prüft zusätzlich file.size in der Action, aber nur als zweite Linie: Die Grenze des Frameworks greift vorher. Anmeldung, Berechtigung und Eingabeprüfung in der Action beschreibt die Seite für Bonn.

#Route Handler für Uploads

Ein Route Handler liest Formulardaten mit request.formData(). Das passt, wenn der Upload von einer mobilen App, einem Partnersystem oder einem Skript kommt und nicht von einem React-Formular. Grundlagen zu Route Handlern stehen auf der Seite für Wuppertal.

// app/api/dokumente/route.ts
export async function POST(request: Request) {
  // Sitzung und Berechtigung zuerst prüfen
  const formData = await request.formData()
  const datei = formData.get('datei')
  if (!(datei instanceof File)) {
    return Response.json({ fehler: 'Keine Datei' }, { status: 400 })
  }
  if (datei.size > 5 * 1024 * 1024) {
    return Response.json({ fehler: 'Datei zu groß' }, { status: 413 })
  }
  // weiter: Typ prüfen, in den Eingangsbereich schreiben
  return Response.json({ name: datei.name, bytes: datei.size })
}

Die Auslese habe ich mit Node 24 gegen ein gebautes Request mit FormData geprüft: instanceof File, size und type kommen wie erwartet an. Eine laufende Next.js-Instanz habe ich dafür nicht aufgesetzt.

Steht vor dem Handler eine proxy.ts, gibt es eine zweite Grenze. Laut proxyClientMaxBodySize puffert Next.js den Body dann im Speicher, standardmäßig bis 10 MB. Darüber wird nur der Anfang gepuffert, eine Warnung geschrieben und die Anfrage läuft weiter, ohne Fehler an den Client. Ein Handler bekäme also eine abgeschnittene Datei. Wer große Uploads annimmt, sollte das wissen.

Server ActionRoute HandlerDirekter Upload
AufruferFormular der eigenen Anwendungbeliebiger HTTP-ClientBrowser, direkt zum Speicher
Größenlimit1 MB, einstellbarje nach Hosting, hinter proxy.ts 10 MB Puffervom Speicher bestimmt
Dateien laufen durch den Next.js-Serverjajanein
Passt fürkleine Dokumente, ProfilbilderSchnittstellen für Drittegroße Dateien, Videos, Scans

#Direkt in den Objektspeicher mit vorsignierter URL

Laufen große Dateien durch den Anwendungsserver, belegen sie dort Arbeitsspeicher und Laufzeit. Die Alternative: Der Server erzeugt eine vorsignierte URL, der Browser lädt die Datei direkt in den S3-kompatiblen Speicher. Die Cloudflare-Dokumentation zu R2 beschreibt das Prinzip, das sie als S3-Konzept bezeichnet: Eine vorsignierte URL erlaubt jedem, der sie besitzt, eine bestimmte Operation auf ein bestimmtes Objekt bis zum Ablauf. Die Gültigkeit reicht dort von einer Sekunde bis zu 7 Tagen.

Aus dieser Beschreibung ergeben sich meine Regeln:

  • Die URL ist ein Bearer-Token. Die Dokumentation sagt es selbst, und sie lässt sich bis zum Ablauf mehrfach verwenden. Ich wähle wenige Minuten Gültigkeit.
  • Den Schlüssel vergibt der Server. Laut AWS-Dokumentation ersetzt S3 ein vorhandenes Objekt mit gleichem Schlüssel. Ein vom Nutzer gewählter Dateiname könnte also eine fremde Datei überschreiben. Ich nehme eine UUID.
  • Der Content-Type wird mitsigniert. Cloudflare nennt das als Schutz: Sendet der Client beim Upload einen anderen Typ, scheitert die Anfrage mit 403/SignatureDoesNotMatch.
  • CORS gehört in die Bucket-Konfiguration, sonst blockiert der Browser den Upload von Ihrer Domain.
  • POST-Formulare sind bei R2 nicht vorgesehen. Die Dokumentation sagt ausdrücklich, dass Multipart-Uploads per HTML-Formular dort nicht unterstützt werden. Es bleibt der PUT.
'use server'
import { randomUUID } from 'node:crypto'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { auth } from '@/lib/auth'

const s3 = new S3Client({
  region: 'auto', // gilt für R2; bei anderen Anbietern steht die Region in deren Doku
  endpoint: process.env.S3_ENDPOINT,
  credentials: {
    accessKeyId: process.env.S3_KEY_ID!,
    secretAccessKey: process.env.S3_SECRET!,
  },
})

export async function uploadUrlAnfordern() {
  const session = await auth()
  if (!session?.user) return { fehler: 'Bitte melden Sie sich an.' }
  const key = `eingang/${session.user.id}/${randomUUID()}.pdf`
  const url = await getSignedUrl(
    s3,
    new PutObjectCommand({
      Bucket: process.env.S3_BUCKET,
      Key: key,
      ContentType: 'application/pdf',
    }),
    { expiresIn: 300 },
  )
  return { url, key }
}

Der Browser sendet danach fetch(url, { method: 'PUT', headers: { 'Content-Type': 'application/pdf' }, body: datei }). Das Paket @aws-sdk/client-s3 steht laut serverExternalPackages auf der Liste der Pakete, die Next.js automatisch nicht bündelt. Eine Konfiguration dafür brauchen Sie nicht.

Eine vorsignierte URL begrenzt Operation, Objekt und Zeit, aber nicht von sich aus die Größe der Datei. Deshalb lese ich nach dem Upload die Metadaten des gespeicherten Objekts (HEAD wird von vorsignierten URLs ebenfalls unterstützt) und verwerfe zu große Dateien. Das ist mein Vorgehen, keine Vorgabe der Dokumentation.

Setzen Sie eine Content Security Policy ein, muss der Host des Speichers in connect-src stehen. MDN beschreibt, dass diese Direktive fetch() und XMLHttpRequest einschränkt. Den Aufbau einer Richtlinie zeigt die Seite für Frankfurt.

#Typ und Schadsoftware prüfen

Das Dateifeld mit accept="application/pdf" hilft Nutzern bei der Auswahl, mehr nicht. MDN schreibt, accept validiere den Dateityp nicht und Nutzer könnten die Einschränkung meist umgehen, weshalb die Prüfung serverseitig erfolgen müsse. Ich lese daher die ersten Bytes der Datei und vergleiche sie mit dem erwarteten Format, statt dem Namen oder dem vom Browser gemeldeten Typ zu glauben.

Für Schadsoftware nutze ich einen Scanner wie ClamAV. Dessen Daemon clamd nimmt laut Protokolldokumentation mit dem Befehl INSTREAM Dateiinhalte in Blöcken entgegen: jeder Block mit einer 4-Byte-Länge, am Ende ein leerer Block. Die Gesamtgröße begrenzt StreamMaxLength in der clamd.conf, bei Überschreitung antwortet der Daemon mit einem Fehler. Eine saubere Datei meldet stream: OK, ein Fund FOUND.

Der Ablauf, den ich dafür einrichte:

  1. Die Datei landet im Bereich eingang/, den keine Seite und kein Link ausliefert.
  2. Ein Prüfschritt streamt sie an clamd und liest die Größe und die ersten Bytes.
  3. Nur bei OK wird sie in den Bereich geprueft/ verschoben und in der Datenbank freigegeben. Bei FOUND wird sie gelöscht und der Vorgang protokolliert.

Die ClamAV-Dokumentation warnt, clamd schütze und authentifiziere Verkehr über den TCP-Socket nicht und werde Befehle von jeder erreichbaren Quelle annehmen. Der Daemon gehört also in ein internes Netz oder an einen lokalen Unix-Socket. Dass der Scanner einen Dienst braucht, der dauernd läuft, spricht für Self-Hosting, wie es die Seite für Berlin beschreibt.

#PDFs erzeugen

Rechnungen, Bestätigungen und Berichte entstehen oft erst in der Anwendung. Zwei Wege sind verbreitet.

@react-pdf/rendererPlaywright page.pdf()
PrinzipPDF aus React-KomponentenPDF aus einer gerenderten HTML-Seite
AusgabeDatei, Stream oder Blob, in Node oder im ClientPDF-Buffer
LayoutFlexbox über Yoga, eigene Elementedie CSS-Regeln für Druck
Next.jsauf der Liste der automatisch ausgenommenen Paketeplaywright und playwright-core stehen ebenfalls darauf

Die Angaben stammen von der React-PDF-Startseite, der Playwright-Referenz und der Liste in der Next.js-Dokumentation. Für Playwright merke ich mir zwei Standardwerte aus der Referenz: Das Format ist Letter und die Ränder sind null. Für deutsche Dokumente gehört format: 'A4' und ein Rand ausdrücklich in den Aufruf. Zudem erzeugt page.pdf() das PDF mit Druck-CSS, nicht mit Bildschirm-CSS, solange nicht vorher page.emulateMedia({ media: 'screen' }) aufgerufen wird.

const pdf = await page.pdf({
  format: 'A4',
  margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' },
})

Die Erzeugung dauert länger als ein normaler Request. Mit export const maxDuration = 30 in der Route legen Sie fest, wie lange Server-Code laufen darf, wobei laut Dokumentation Hosting-Plattformen den Wert aus dem Build übernehmen können. Was nicht in einer Anfrage fertig wird, gehört in einen Hintergrundjob.

#Geschützte Downloads

Dokumente aus dem Eingangs- und Prüfbereich liegen nicht im Ordner public. Wie ein Route Handler die Sitzung prüft und die Datei mit Content-Disposition und Cache-Control: private ausliefert, zeigt die Seite für Bielefeld. Liegt die Datei im Objektspeicher, kann der Server stattdessen eine vorsignierte GET-URL mit kurzer Gültigkeit erzeugen und weiterleiten. Dann gilt dasselbe wie oben: Wer die URL hat, darf sie bis zum Ablauf nutzen.

#Wenn die Unterlagen personenbezogen sind

Die zuständige Aufsicht in Nordrhein-Westfalen ist die LDI NRW. Zu Ausweiskopien schreibt sie in ihrer Übersicht Personalausweis und Datenschutz, man sei grundsätzlich nicht verpflichtet, eine Kopie vorzulegen oder zu überlassen. Nach § 20 Absatz 2 PAuswG darf der Ausweis nur vom Inhaber oder mit dessen Zustimmung abgelichtet werden, und die Kopie muss eindeutig und dauerhaft als Kopie erkennbar sein. Die LDI NRW verweist auf den Grundsatz der Datenminimierung: Zuerst fragen, ob eine Kopie überhaupt nötig ist.

Für bestimmte Verpflichtete, zu denen die Behörde Versicherungsunternehmen, Steuerberater und Immobilienmakler zählt, gelten nach dem Geldwäschegesetz eigene Identifizierungs- und Aufbewahrungspflichten. Die LDI NRW nennt für die Aufzeichnungen fünf Jahre Aufbewahrung nach § 8 Absatz 4 GwG. Ob Ihr Betrieb dazugehört, ist eine Rechtsfrage, die ich nicht beantworte.

Für Gesundheitsdaten gilt Artikel 9 Absatz 1 der DSGVO: Ihre Verarbeitung ist grundsätzlich untersagt, solange kein Fall des Absatzes 2 greift, etwa eine ausdrückliche Einwilligung. Technisch heißt das für mich: Zugriff nur für Berechtigte, kurze Aufbewahrung, Löschung nach dem Zweck und Protokolle ohne Dateiinhalte. Die LDI NRW stellt für kleine und mittlere Betriebe Vorlagen und Hinweise bereit, darunter zu technischen und organisatorischen Maßnahmen (Datenschutz im KMU).

#Checkliste für Ihren Upload

FrageWarum
Wie groß ist die größte erlaubte Datei, und passt sie in 1 MB samt Multipart-Overhead?Sonst scheitert der Request vor Ihrem Code
Läuft eine proxy.ts vor dem Handler?Ab 10 MB wird der Body nur abgeschnitten gepuffert
Wer vergibt den Objektschlüssel?Gleicher Schlüssel überschreibt das vorhandene Objekt
Wie lange gilt die vorsignierte URL?Sie ist ein Bearer-Token und mehrfach nutzbar
Wo liegt eine Datei bis zur Prüfung?Nicht dort, wo sie jemand abrufen kann
Wo läuft der Virenscanner, und wer erreicht ihn?clamd schützt seinen TCP-Socket nicht selbst
Welches Papierformat nutzen die PDFs?Playwright startet mit Letter
Brauchen Sie die Kopie wirklich?Datenminimierung nach der LDI NRW

#So läuft ein Projekt mit mir

Sie schicken mir eine schriftliche Beschreibung: welche Dokumente von wem kommen, wie groß sie werden und wo sie später gebraucht werden. Ich antworte mit Rückfragen und danach mit einem schriftlichen Umfang samt Annahmen. Die Umsetzung läuft auf einer Testumgebung, die Sie abnehmen, bevor etwas live geht. Zugänge, Texte und das Layout bringen Sie mit, ich baue nach Ihrer Vorlage. Auf Wunsch betreue ich die Anwendung danach weiter, getestete Updates inklusive. Meetings und Telefonate gibt es nicht, alles bleibt schriftlich nachlesbar.

Weitere Themen rund um Next.js stehen auf den Seiten für Dresden (Datenbank), Essen (Fehlerbehandlung ohne Personendaten) und Düsseldorf (Bilder).

#Häufige Fragen zu Uploads in Next.js

#Wie groß darf eine Datei sein, die ich über eine Server Action hochlade?

Standardmäßig 1 MB für den gesamten Request-Body. Die Dokumentation nennt dafür die Option serverActions.bodySizeLimit und weist darauf hin, dass der Wert den rohen HTTP-Body einschließlich der Multipart-Ränder meint. Bei Uploads nahe am Limit rechne ich 10 bis 20 KB Reserve ein. Sind größere Dateien nötig, wähle ich einen direkten Upload in den Objektspeicher.

#Ist ein direkter Upload mit vorsignierter URL sicher?

Nur, wenn die URL kurz gilt und genau eine Operation auf genau ein Objekt erlaubt. Laut Cloudflare-Dokumentation ist eine vorsignierte URL ein Bearer-Token: Wer sie kennt, darf die Operation bis zum Ablauf ausführen, auch mehrfach. Deshalb vergebe ich den Objektschlüssel auf dem Server, setze eine kurze Gültigkeit und signiere den Content-Type mit.

#Reicht das accept-Attribut am Dateifeld als Typprüfung?

Nein. MDN schreibt, dass accept den Typ nicht prüft, sondern dem Browser nur einen Hinweis gibt, und dass Nutzer die Auswahl meist umgehen können. Die Prüfung gehört auf den Server.

#Wie prüfe ich hochgeladene Dateien auf Schadsoftware?

Mit einem Virenscanner wie ClamAV. Dessen Daemon clamd nimmt Dateiinhalte per INSTREAM entgegen und antwortet pro Datei mit OK oder FOUND. Die Datei liegt bis dahin in einem Eingangsbereich, den keine Seite ausliefert. Der Daemon gehört nicht ans offene Internet, die ClamAV-Dokumentation warnt ausdrücklich vor einem ungeschützten TCP-Socket.

#Welche Papiergröße erzeugt Playwright standardmäßig für PDFs?

Letter, mit Rändern von null. Für deutsche Dokumente setze ich format auf A4 und lege Ränder fest. Das steht in der Playwright-Referenz zu page.pdf.

#Darf ein Formular eine Kopie des Personalausweises verlangen?

Das hängt vom Zweck ab. Nach der LDI NRW sind Sie grundsätzlich nicht verpflichtet, eine Kopie zu überlassen, und § 20 Absatz 2 des Personalausweisgesetzes verlangt, dass eine Kopie eindeutig und dauerhaft als Kopie erkennbar ist. Ausnahmen gibt es etwa für Verpflichtete nach dem Geldwäschegesetz. Ob Ihr Fall dazugehört, klären Sie mit Ihrer Rechtsberatung oder der Datenschutzbeauftragten.

#Ihr Upload in einer E-Mail

Schreiben Sie mir über das Kontaktformular. Nennen Sie die Adresse der Anwendung oder Ihr Vorhaben, die Dateitypen und die größte erwartete Datei, den Hoster oder Speicher und Ihren Wunschtermin. Ich antworte werktags schriftlich, und die erste schriftliche Einschätzung kostet nichts.

Brauchen Sie die Leistung: Next.js-Entwickler in Münster?

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

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken