Next.js und Symfony als Full-Stack-Architektur: Der Entscheidungsleitfaden für dein nächstes Projekt

Sandor Farkas
Gründer & Lead Developer
Experte für Softwareentwicklung und Legacy-Code-Optimierung
LinkedInWolf-Tech baut die meisten Kundenprojekte auf derselben Kombination auf: Next.js im Frontend, Symfony als API-Backend. Das ist nicht der einzige Weg, ein SaaS-Produkt zu bauen, und nicht die richtige Wahl für jedes Team, aber für eine B2B-Anwendung, die über den Wochenend-Prototypen hinaus skalieren muss, hält die Next.js-Symfony-Architektur besser stand als die Alternativen, mit denen wir sie üblicherweise vergleichen sollen. Dieser Leitfaden macht den Fall dafür als Full-Stack-Architektur und legt den Blueprint dar, um sie zusammenzusetzen.
Warum kein reiner PHP-Full-Stack
Symfony kann das Frontend auch rendern. Twig-Templates, Stimulus für Interaktivität, vielleicht eine Prise Turbo für Seitenübergänge. Das funktioniert, und für inhaltslastige Websites oder interne Tools ist es oft die einfachere Wahl.
Wo es anfängt zu klemmen, ist bei komponentenlastigen UIs: Dashboards mit Live-Filterung, mehrstufige Formulare mit bedingten Feldern, alles, was sich eher wie eine Anwendung als wie ein Dokument verhält. Twig wurde nicht für diese Art von State-Management gebaut, und ein JavaScript-Framework auf serverseitig gerenderte Templates zu schrauben, führt eher zu zwei halbfertigen Systemen als zu einem funktionierenden.
Das Komponenten-Ökosystem von React existiert, weil dieses Problem häufig genug ist, um eine ganze Tooling-Kategorie darum herum zu rechtfertigen. TypeScript fängt eine Kategorie von Bugs ab, die PHPs Typsystem, selbst mit aktivierten Strict Types, über die Request-Grenze hinweg in den Browser nicht erreicht. Und Next.js gibt dir unabhängiges Frontend-Deployment: Du kannst einen UI-Fix ausliefern, ohne die API anzufassen, was wichtig wird, sobald mehr als eine Person im Team ist.
Warum kein reiner Node.js-Full-Stack
Die entgegengesetzte Frage kommt genauso oft auf: Wenn du für das Frontend ohnehin TypeScript schreibst, warum nicht das gesamte Backend auch in Node schreiben?
Für viele Projekte ist das eine vernünftige Antwort. Wo Symfony sich seinen Platz verdient, ist in der Komplexität des Backends selbst. Symfonys Dependency-Injection-Container und seine Konvention zur Organisation von Services skalieren gut, wenn eine Codebase über eine Handvoll Engineers hinauswächst - auf eine Weise, wie es eine lose strukturierte Express- oder Fastify-App oft nicht ohne viel selbst auferlegte Disziplin schafft. Doctrine ORM handhabt komplexe relationale Datenmodelle, Migrationen und Query-Building mit einer Reife, die die meisten Node-ORMs erst noch aufholen. Und PHPs Request-Lebenszyklus - ein frischer Prozess pro Request statt einer einzigen lang laufenden Event-Loop - gibt dir eine sauberere Isolationsgrenze für Multi-Tenant-SaaS, bei der die langsame Query eines Mandanten nicht Requests von jedem anderen Mandanten, der sich den Prozess teilt, blockieren dürfen sollte.
Keines dieser Argumente ist absolut. Ein Team, das ausschließlich TypeScript nutzt und das auch so beibehalten möchte, hat einen legitimen Grund, stattdessen ein Node-Backend zu wählen. Aber wenn das Team bereits PHP-Erfahrung mitbringt oder das Datenmodell des Produkts wirklich komplex ist, ist Symfony im Backend kein Kompromiss. Es ist das stärkere Werkzeug für diese Hälfte der Aufgabe.
Die Integrationsarchitektur
Die sauberste Version dieser Kombination behandelt Symfony als reine JSON-API - entweder über API Platform oder über eigene Controller, die serialisierte Antworten zurückgeben, ganz ohne serverseitig gerenderte Views. Next.js ist das Einzige, das HTML erzeugt.
Auf der Next.js-Seite teilt sich das Daten-Fetching in zwei Wege. Server Components holen Daten direkt von der Symfony-API während des Server-Renderings, was API-Keys und interne Endpunkte vom Client fernhält und dafür sorgt, dass die initiale Seite bereits mit Daten geladen ankommt. Client-Components, die nach dem Laden der Seite neu abrufen, paginieren oder auf Nutzerinteraktion reagieren müssen, nutzen React Query gegen dieselbe API, was dir Caching und Hintergrund-Refetching gibt, ohne es selbst zu bauen.
Authentifizierung läuft über JWT-Tokens, die von Symfony ausgestellt werden, typischerweise via LexikJWTAuthenticationBundle, wobei das Token in einem httpOnly-Cookie statt im Local Storage gespeichert wird, damit es für clientseitiges JavaScript nicht erreichbar ist. Next.js-Middleware prüft das Token auf geschützten Routen, noch bevor die Seite überhaupt rendert.
Das Problem der Typ-Diskrepanz, bei dem die Form der API von dem abweicht, was das Frontend erwartet, wird einmal gelöst und bleibt dann meist gelöst: Symfony (via API Platform oder nelmio/api-doc-bundle) generiert eine OpenAPI-Spezifikation, und ein Codegenerierungs-Schritt verwandelt diese Spezifikation in TypeScript-Typen für die Next.js-Seite. Führe das in der CI aus, und eine Feldumbenennung im Backend wird zu einem Typfehler im Frontend-Build statt zu einem Laufzeitfehler, der drei Wochen später in Produktion auftaucht.
Die Deployment-Architektur
Next.js und Symfony werden als separate Container deployt, was auch den Vorteil des unabhängigen Deployments real statt nur theoretisch macht. Ein typisches Setup:
Der Next.js-Container betreibt die App im Standalone-Output-Modus hinter Nodes eingebautem Server, oder hinter Nginx, wenn du eine Caching-Schicht davor haben willst. Der Symfony-Container betreibt PHP-FPM, üblicherweise ebenfalls hinter Nginx, und stellt die API unter so etwas wie api.deineapp.com oder einem /api-Pfadsegment bereit, je nachdem, wie du Cookies und CORS verhalten lassen willst.
Beide Container teilen sich eine PostgreSQL-Instanz für die Daten der Anwendung und eine Redis-Instanz für Symfonys Cache, Sessions und Messenger-Transport, falls du asynchrone Jobs nutzt. Nginx, oder ein Load Balancer vor beiden Containern, routet Requests basierend auf Pfad oder Subdomain an den richtigen Service. Das ist dasselbe Muster, das wir für Individualsoftware-Entwicklung-Projekte nutzen, die über einen einzelnen VPS hinaus skalieren müssen: Jedes Teil kann unabhängig skaliert, neu deployt oder ersetzt werden, sobald es sich keinen Prozess mehr mit allem anderen teilt.
Das Monorepo-Setup
Beide Hälften in einem Repository zu halten, macht koordinierte Änderungen einfacher, ohne die Codebases zu einer einzigen deploybaren Einheit zusammenzuführen. Ein funktionierendes Layout legt die Symfony-App unter /api und die Next.js-App unter /web, wobei Composer die PHP-Abhängigkeiten innerhalb von /api verwaltet und ein npm-Workspace die Next.js-Seite unter /web. Das OpenAPI-Typgenerierungs-Skript läuft als Pre-Build-Schritt vom Repo-Root aus, liest die Spezifikation aus der Symfony-App und schreibt Typen in den Source-Tree der Next.js-App.
CI führt bei jedem Pull-Request beide Test-Suiten aus, unabhängig davon, welche Seite sich geändert hat, da eine Backend-Änderung durch die generierten Typen einen Frontend-Build kaputt machen kann, selbst wenn keine Frontend-Dateien angefasst wurden. Das Deployment bleibt getrennt: Ein Merge in main kann ein Next.js-Deployment, ein Symfony-Deployment oder beides auslösen, je nachdem, welche Pfade sich geändert haben.
Pfadbasierte CI-Trigger verhindern, dass das langsamer wird, als es sein müsste. Ein GitHub-Actions-Workflow, der prüft, ob ein Commit /api berührt hat, bevor er PHPUnit ausführt, und separat, ob er /web berührt hat, bevor er die Next.js-Test-Suite und den Build ausführt, bedeutet, dass eine reine Frontend-Änderung nicht auf einen PHP-Testlauf wartet, den sie ohnehin nicht kaputt machen kann, und umgekehrt. Die einzige Ausnahme ist der OpenAPI-Generierungsschritt, der immer laufen sollte, da das der Teil ist, der die grenzüberschreitenden Brüche abfängt, die die getrennten Pipelines sonst übersehen würden.
Die lokale Entwicklung profitiert von derselben Trennung. Eine docker-compose.yaml im Repo-Root, die Postgres, Redis, den Symfony-Container mit aktiviertem Xdebug und den Next.js-Dev-Server gleichzeitig hochfährt, bedeutet, dass ein neuer Engineer das Repo klonen und mit einem einzigen Befehl eine funktionierende Umgebung haben kann, ohne separat eine PHP-Umgebung und eine Node-Umgebung auf der eigenen Maschine einzurichten.
Ist das der richtige Stack für dein Projekt
Wenn dein Team bereits PHP kennt und ihr etwas mit einem echten Datenmodell dahinter baut - Rechnungen, Abonnements, Multi-Tenant-Berechtigungen -, lässt dich diese Kombination Symfony für den Teil nutzen, für den es gut ist, und Next.js für den Teil, für den es gut ist, ohne eines der beiden zu einer Aufgabe zu zwingen, für die es nicht gebaut wurde. Wenn dein Team ausschließlich JavaScript nutzt und das Backend wirklich einfach ist, bringt dich ein reiner Node-Stack mit weniger zu lernendem Aufwand dorthin.
Wenn du gerade mittendrin in dieser Entscheidung steckst, oder dich bereits auf eine Seite festgelegt hast und mit der anderen in Reibung gerätst, reicht meist eine Tech-Stack-Strategie-Session, um herauszufinden, ob die Reibung ein echter Architektur-Mismatch oder ein behebbares Implementierungsproblem ist. Melde dich unter hello@wolf-tech.io oder besuche wolf-tech.io, wenn du eine zweite Meinung willst, bevor du dich festlegst.
