21. März 2026
React-Projektstruktur: Von MANTRA zu modernen Frameworks
Ein Ruckblick darauf, wie wir 2017 React-Apps mit der MANTRA-Architektur strukturiert haben, welche Probleme sie loste und wie moderne Frameworks wie Next.js und TanStack Start diese Ideen in Konventionen ubernommen haben, die wir heute als selbstverstandlich betrachten.
Sascha Becker
Author11 Min. Lesezeit

React-Projektstruktur: Von MANTRA zu modernen Frameworks
2017 habe ich einen Artikel geschrieben: Structure Your React Apps the Mantra Way. Es war die Zeit von Create React App, Redux-Boilerplate und react-router-dom v4. Next.js existierte zwar, war aber noch eine Nische. Ordnerstrukturen waren der Wilde Westen.
Der Artikel schlug eine modulbasierte Architektur vor, inspiriert von Mantra JS, einer Anwendungsspezifikation von Kadira aus der Meteor-Ara. Sie war eigenwillig, strukturiert und loste echte Probleme, mit denen Teams taglich zu kampfen hatten. Wenn ich den Artikel fast ein Jahrzehnt spater nochmal lese, fallt mir auf, wie viele dieser Ideen zum Mainstream geworden sind und wie viel Zeremonie moderne Frameworks eliminiert haben.
Dieser Beitrag ist eine Retrospektive. Woher wir kamen, wo wir jetzt stehen und warum der Weg dorthin wichtig ist.
Das Problem 2017
React gab dir Komponenten und eine Render-Funktion. Alles andere war dein Problem. Wie man Daten abruft, wo der State hinkommt, wie man Routen verdrahtet und, am umstrittensten, wie man seine Dateien organisiert. Dan Abramov fasste die Stimmung treffend zusammen mit einer eigenen Website, deren gesamter Ratschlag lautete: "move files around until it feels right." Das war kein Witz. Es gab schlicht keinen Konsens.
Der erste Instinkt war, nach Dateityp zu gruppieren:
srccomponentsGames.jsGameTile.jsHeader.jscontainersGamesContainer.jsHomeContainer.jsactionsgameActions.jscoreActions.jsreducersgameReducer.jscoreReducer.jsroutesAppRoutes.js
Das sieht anfangs ordentlich aus. Es zerfallt in dem Moment, in dem man zwanzig Features hat. Muss man andern, wie Games funktionieren? Funf Ordner anfassen. Ein Feature loschen? Viel Gluck, alle Teile zu finden.
Wie ich damals schrieb:
Der MANTRA-Ansatz
MANTRA drehte die Struktur von "gruppieren nach Typ" auf "gruppieren nach Geschaftslogik." Jedes Feature wurde ein eigenstandiges Modul mit eigenen Komponenten, Containern, Actions, Reducern und Routen:
modulescorecomponentscontainersactionsreducersroutesindex.jsgamescomponentscontainersactionsreducersroutesindex.jscontactcomponentscontainersactionsreducersroutesindex.js
Die Regeln waren einfach:
- Geschaftslogik zuerst. Jedes Modul besitzt alles, was es braucht.
- Keine kaskadierten Module. Wenn ein Modul ein Submodul braucht, wird es als Geschwister erstellt, nicht als Kind. Wie ich es formulierte: "you don't cascade modules. If a module should have a submodule create it separately. The advantage is that you see all modules at a glance."
- Explizite offentliche API. Jedes Modul stellt seine Teile uber eine
index.jsbereit:
jsimport * as actions from "./actions";import reducers from "./reducers";import routes from "./routes";export { actions, reducers, routes };
- Verbindung an der Spitze. Eine Root-Datei kombinierte alle Modul-Reducer, eine andere verknupfte alle Routen:
AppRoutes.jsimport { routes as home } from "./modules/home";import { routes as games } from "./modules/games";import { routes as team } from "./modules/team";export default (store) => {return (<Switch><Application>{home(store)}{games(store)}{team(store)}</Application></Switch>);};
Die Vorteile waren real. Isolation bedeutete, dass man ein Modul austauschen konnte, ohne andere zu brechen. Import-Pfade blieben kurz. Die Arbeit an einem Feature belastete nicht das mentale Modell eines anderen. Wie ich zusammenfasste:
Was wir wirklich gelost haben
Ruckblickend loste MANTRA vier verschiedene Probleme, die React und sein Okosystem offen liessen:
- Feature-Isolation. Wo lebt der gesamte Code fur "Games"? An einem Ort, nicht verstreut uber typbasierte Ordner.
- Route-Komposition. Wie fugen wir eine neue Seite hinzu? Indem wir ein Modul erstellen und seine Routen in einer Verbindungsdatei registrieren.
- State-Grenzen. Jedes Modul besitzt seine Reducer und Actions. Keine globale Suppe aus unzusammenhangenden States.
- Datenfluss-Klarheit. Die Container/Component-Trennung machte explizit, woher Daten kamen und wo die Prasentation lebte.
Jedes einzelne dieser Probleme wurde seitdem durch Framework-Konventionen adressiert.
Die moderne Antwort
Dateisystem-Routing ersetzte manuelles Route-Wiring
2017 exportierte jedes Modul eine Route-Funktion, die <Route>-Komponenten zuruckgab, und eine Top-Level-Datei nahte sie zusammen. Es funktionierte, aber eine neue Seite hinzuzufugen bedeutete, mindestens zwei Dateien zu bearbeiten: die Routen des Moduls und die zentrale AppRoutes.js.
Next.js App Router, TanStack Start und React Router (v7, Framework-Modus) nutzen alle das Dateisystem als Router. Datei erstellen, Route bekommen:
app(auth)loginpage.tsxregisterpage.tsxlayout.tsx(dashboard)layout.tsxgamespage.tsxloading.tsx_componentsGameTile.tsxactions.tsorderspage.tsx_componentsOrderTable.tsxactions.ts(marketing)layout.tsxpage.tsxpricingpage.tsx
Next.js-Konventionen
Ordner in Klammern wie (auth) sind Route
Groups:
Sie organisieren Code, ohne die URL zu beeinflussen. Ordner mit _-Prafix wie
_components sind private
Ordner:
Sie werden vom Routing ausgeschlossen. Beides sind Next.js-Konventionen, um
Feature-Code kolokiert zu halten.
Die Verbindungsdatei ist weg. Die Modulgrenze ist einfach ein Ordner. Route-Registrierung ist implizit.
Server Components ersetzten das Container-Pattern
Die Container/Presentational-Trennung war das dominante React-Pattern 2017. Container verbanden sich mit Redux, holten Daten und reichten sie nach unten. Presentational Components waren "dumm" und renderten nur Props.
js// 2017: Container verbindet sich mit Redux und reicht Daten weiterclass Container extends Component {componentDidMount() {this.props.dispatch(coreActions.setMenuIndex(1));}render() {return <Games {...this.props} />;}}export default connect((state) => {return { mobile: state.core.responsive.mobile };})(Container);
Mit React Server Components ist die Server-Komponente die Datenschicht. Kein Wrapper notig:
tsx// 2026: Server Component fetcht direktexport default async function GamesPage() {const games = await getGames();return <GameList games={games} />;}
Mit TanStack Start ubernimmt ein Loader auf der Route-Definition die gleiche Aufgabe:
tsx// TanStack Startexport const Route = createFileRoute("/games")({loader: async () => ({ games: await getGames() }),component: GamesPage,});
React Router v7 (ehemals Remix) nutzt ein ahnliches Konzept mit einem benannten Export:
tsx// React Router v7 (Framework-Modus)export const loader = async () => {const games = await getGames();return { games };};
Das Container-Pattern starb nicht, weil es falsch war. Es starb, weil das Framework seine Verantwortung ubernahm.
Kolokierte Actions ersetzten Redux-Module
Jedes MANTRA-Modul hatte eigene actions/- und reducers/-Ordner. Action Types, Action Creators und Reducer-Funktionen waren uber mehrere Dateien pro Feature verteilt. Eine einzige Benutzerinteraktion hinzuzufugen bedeutete, drei oder vier Dateien anzufassen.
actionTypes.jsexport const MENU_TOGGLE = "MENU_TOGGLE";export const SET_MENU_INDEX = "SET_MENU_INDEX";// actions.jsexport function toggleMenu(open) {return { type: TYPES.MENU_TOGGLE, open };}// reducer.jsexport default function (state = defaultState, action) {switch (action.type) {case TYPES.MENU_TOGGLE:return toggleMenu(state, action);// ...}}
Heute leben Server Actions direkt neben der Seite, die sie nutzt:
app/games/actions.ts"use server";export async function toggleFavorite(gameId: string) {await db.game.update({ where: { id: gameId }, data: { favorite: true } });revalidatePath("/games");}
Eine Datei. Keine Action Types, kein Dispatch, kein Reducer-Boilerplate. Fur Client State ersetzt ein kleiner Zustand- oder Jotai-Store, was fruher ein ganzes Redux-Modul war.
Das Index.js-Pattern wurde uberflussig
MANTRAs index.js pro Modul war die offentliche API: Sie re-exportierte Actions, Reducer und Routen, damit andere Module uber einen sauberen Pfad importieren konnten. Das war gute Praxis fur die Aufrechterhaltung von Grenzen.
In einem Framework mit Dateisystem-Konventionen werden diese Grenzen vom Framework selbst durchgesetzt. Dateien wie page.tsx und layout.tsx haben jeweils eine bekannte Rolle. Es gibt nichts zu re-exportieren, weil das Framework weiss, wo es suchen muss.
Was sich bewahrt hat
Nicht alles musste ersetzt werden. Einige von MANTRAs Ideen sind heute akzeptierte Weisheit.
Feature-basierte Organisation ist der Standard. Ob man sie Module, Features oder Route Segments nennt: Die Industrie hat sich auf "gruppieren nach Geschaftslogik" geeinigt. Der Next.js App Router ist im Wesentlichen MANTRAs Modulstruktur, durchgesetzt durch Konvention.
Flache Modul-Hierarchien. Die Regel gegen kaskadierte Module passt direkt dazu, wie Route Groups in modernen Frameworks funktionieren. Tief verschachtelte Feature-Ordner sind immer noch ein Anti-Pattern. Dinge flach und auf einen Blick sichtbar zu halten, ist immer noch guter Rat.
Kolokation. Komponenten, ihre Datenlogik, ihre Styles und ihre Tests leben nebeneinander. Das war 2017 ein neuartiger Ratschlag. 2026 ist es selbstverstandlich.
Explizite Grenzen zwischen Features. Auch ohne index.js-Re-Exports bleibt das Prinzip bestehen, Module voneinander zu isolieren. Ob man es durch Ordner-Konventionen, Barrel Files oder ESLint-Import-Regeln durchsetzt: Die Idee ist dieselbe.
Was ich meinem 2017er-Ich sagen wurde
Die Architektur war solide. Der Instinkt, nach Feature zu organisieren, Module flach zu halten und klare Grenzen zu pflegen, war genau richtig. Die Umsetzung erforderte nur eine Menge manueller Klempnerarbeit, die Frameworks heute ubernehmen.
Ich habe damals auch ein npm-Paket namens module-loader geschrieben, um den Setup-Aufwand zu automatisieren. Es war der richtige Impuls: Das Boilerplate war der schwachste Teil. Frameworks kamen letztlich zum gleichen Schluss und eliminierten es komplett.
Wenn du heute ein neues React-Projekt startest, musst du uber das meiste davon nicht nachdenken. Nimm Next.js, TanStack Start oder React Router. Folge den Datei-Konventionen. Deine Projektstruktur ist bereits besser als das, woruber wir 2017 wochenlang diskutiert haben.
Aber wenn du an einer grossen SPA ohne Framework arbeitest (und es gibt immer noch gute Grunde dafur), halten die MANTRA-Prinzipien stand. Gruppiere nach Feature. Halte Module flach. Mache Grenzen explizit. Die Namen andern sich, die Idee nicht.
MANTRA mit Vite (ohne Framework)
Wenn du Vite oder Vite+ ohne ein Meta-Framework verwendest, gibt es keine Dateisystem-Konventionen, auf die du dich stutzen kannst. Du bist wieder in CRA-Territorium, und MANTRAs Struktur lasst sich fast direkt ubertragen, nur mit modernen Werkzeugen anstelle der 2017er-Aquivalente:
srcfeaturesgamescomponentsGameTile.tsxGameList.tsxhooksuseGames.tsapigames.queries.tsroutes.tsxindex.tsorderscomponentsOrderTable.tsxhooksuseOrders.tsapiorders.queries.tsroutes.tsxindex.tssharedcomponentsLayout.tsxhooksuseAuth.ts
Die Form ist vertraut, aber die Inhalte haben sich verandert:
hooks/ersetztcontainers/. Custom Hooks haben die Datenabruf- und State-Logik ubernommen, die Container fruher gehandhabt haben. Keine Class Components mehr, die Class Components wrappen.api/ersetztactions/+reducers/. TanStack Query oder SWR ersetzt Redux fur Server State, sodass jedes Feature nur noch Query- und Mutation-Definitionen hat statt Action Types, Action Creators und Reducer-Funktionen.index.tsist weiterhin wichtig. Ohne Framework-Konventionen, die Grenzen durchsetzen, ist die Barrel-Datei wieder deine offentliche API, genau wie MANTRAs ursprunglicheindex.js.routes.tsxmuss weiterhin manuell verdrahtet werden. Du musst Feature-Routen immer noch in einem Root-Router zusammenfugen, genau wie in der altenAppRoutes.js. React Router oder TanStack Router ubernehmen das Rendering, aber die Komposition liegt bei dir.
Die Zeremonie ist leichter (kein Redux-Boilerplate, keine Container/Presentational-Trennung), aber die organisatorische Disziplin liegt weiterhin bei dir. Das ist der Kompromiss, wenn man ohne Framework arbeitet: mehr Freiheit, mehr Verantwortung.
Damals und Heute
| Aspekt | 2017 (MANTRA) | 2026 (Frameworks) |
|---|---|---|
| Route-Registrierung | Manuelle Verbindungsdatei mit Modul-Routen | Dateisystem-Routing |
| Datenabruf | Container-Komponenten + Redux connect | Server Components, Loader, Server Actions |
| State Management | Redux Actions + Reducer pro Modul | Server Actions + leichtgewichtige Stores (Zustand, Jotai) |
| Modulgrenze | index.js-Re-Exports | Ordner-Konventionen + Framework-Dateirollen |
| Feature-Isolation | Manuelle Disziplin | Durch Dateisystem-Struktur durchgesetzt |
| Geteilte Logik | Core-Modul mit geteilten Actions | Geteilte Layouts, Middleware, Utility-Ordner |
| Build-Tooling | Create React App, Webpack-Konfiguration | Vite, Turbopack, Zero-Config |
Die Tabelle lasst es wie einen sauberen Austausch aussehen, und in vielen Aspekten ist es das. Aber die mentalen Modelle hinter der 2017er-Spalte sind es, die die 2026er-Spalte moglich gemacht haben. Framework-Autoren haben Feature-basierte Organisation nicht erfunden. Sie haben beobachtet, was Teams bereits taten (oft muhsam, mit manuellem Boilerplate) und es in Konventionen verwandelt.
Schlussgedanke
Ich bin dankbar fur die MANTRA-Ara. Nicht weil der Code besser war (war er nicht), sondern weil das Denken richtig war. Wir haben echte Probleme mit den Werkzeugen gelost, die wir hatten. Dass diese Losungen so weit verbreitet wurden, dass sie in Framework-Konventionen verschwunden sind, ist das bestmogliche Ergebnis.
Jede page.tsx, die du erstellst, ohne daruber nachzudenken, ist ein Problem, das jemand 2017 hart erkampft hat.
- Structure Your React Apps the Mantra Way (2017)
Der Originalartikel, der diese Retrospektive inspiriert hat.
- Mantra JS Specification
Die Anwendungsarchitektur-Spezifikation von Kadira fur Meteor, die den modulbasierten Ansatz inspirierte.
- React File Structure von Dan Abramov
Der beruhmte Einzeiler, der den Stand der Ordnerstruktur-Debatte einfing: 'move files around until it feels right.'
- Next.js App Router Documentation
Der dateisystembasierte Router, der viele der Patterns absorbiert hat, die MANTRA manuell etablierte.
- TanStack Start
Ein modernes Full-Stack React-Framework mit dateibasiertem Routing und typsicheren Server-Funktionen.
