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.
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.

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 Action | Route Handler | Direkter Upload | |
|---|---|---|---|
| Aufrufer | Formular der eigenen Anwendung | beliebiger HTTP-Client | Browser, direkt zum Speicher |
| Größenlimit | 1 MB, einstellbar | je nach Hosting, hinter proxy.ts 10 MB Puffer | vom Speicher bestimmt |
| Dateien laufen durch den Next.js-Server | ja | ja | nein |
| Passt für | kleine Dokumente, Profilbilder | Schnittstellen für Dritte | groß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:
- Die Datei landet im Bereich
eingang/, den keine Seite und kein Link ausliefert. - Ein Prüfschritt streamt sie an
clamdund liest die Größe und die ersten Bytes. - Nur bei
OKwird sie in den Bereichgeprueft/verschoben und in der Datenbank freigegeben. BeiFOUNDwird 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/renderer | Playwright page.pdf() | |
|---|---|---|
| Prinzip | PDF aus React-Komponenten | PDF aus einer gerenderten HTML-Seite |
| Ausgabe | Datei, Stream oder Blob, in Node oder im Client | PDF-Buffer |
| Layout | Flexbox über Yoga, eigene Elemente | die CSS-Regeln für Druck |
| Next.js | auf der Liste der automatisch ausgenommenen Pakete | playwright 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
| Frage | Warum |
|---|---|
| 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.
Ausgewählte Projekte
Beispiele aus meiner Arbeit.
Tech Platform: VECTOR TECHNOLOGIES
VECTOR Technologies ist ein internationales Technologieunternehmen, das sich auf die Entwicklung und Produktion moderner Lösungen für Telekommunikationsanbie...
Travel & Tourism Site: gidpl.ru
Seit dem 25. März 2013 informiert das Portal GIDpl.ru kontinuierlich über die wichtigsten kulturellen und sportlichen Ereignisse sowie über touristische und ...
Travel & Tourism Site: zamki-szkocji.com
zamki-szkocji.com ist ein umfassendes Informationsportal, das sich den Schlössern Schottlands widmet und reichhaltige historische Inhalte, interaktive Karten...
WordPress Support & Entwicklung in Münster
Brauchen Sie die Leistung: Next.js-Entwickler in Münster?
Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.
Projekt beschreibenWeitere WordPress-Dienste und Wissensbasis entdecken
Migration zu Astro, Next.js und Headless WordPress.
WooCommerce-Synchronisierung mit ERP und Großhandel.
Geschäftsprozessautomatisierung rund um WordPress und Firmensysteme.
Bibliothek konkreter Automatisierungsfälle.
Headless WordPress, Sanity, Strapi und Contentful mit Astro oder Next.js.
Astro, MDX, Edge Delivery und an echtem Traffic gemessene Core Web Vitals.
Individuelle WordPress-Entwicklung und Architektur.
Skalierbare Headless-, ERP- und KI-Architektur für Enterprise.
Verwandte Kategorien
Unterstützende Artikel

Sechs bis sechzehn Wochen für typische Projekte, in vier Phasen: Analyse, Scoping, Build und Umstellung, Tuning. Die Variablen sind Katalogumfang, Anzahl der Integrationen, URL-Erhalt und die Bereitschaft des Redaktionsteams, nicht die Wahl des Frameworks.

Umfassende 4-Jahres-TCO-Analyse (Total Cost of Ownership), reale Core-Web-Vitals-Benchmarks, Astro 7 GraphQL APQ-Architektur und eine 10-Punkte-Entscheidungsmatrix für Enterprise-Entscheider zwischen entkoppeltem und traditionellem WordPress.

Die Entscheidung Shopify Plus vs WooCommerce headless im Jahr 2026 ist kein binärer "Plattform vs Custom"-Kompromiss mehr. Beide laufen headless, beide integrieren KI, beide liefern am Edge aus. Die echten Achsen sind Kontrolle, Gesamtkosten über fünf Jahre und Exit-Strategie. Dieser Artikel durchläuft die Entscheidungsmatrix mit bestätigten Plattformfakten.