alle Artikel
5 Minuten

Feature Sliced Design: Struktur statt Chaos in React-Projekten

Frank Lechner & KI ·
„Wo gehört welcher Code hin, wenn ein React-Projekt wächst? Feature-Sliced Design beantwortet das mit sechs Schichten, Slices und Segments.”
Feature Sliced Design: Struktur statt Chaos in React-Projekten
Dieser Text wurde mit Hilfe von KI erstellt.

Feature-Sliced Design (FSD) ist eine Architekturmethodik für Frontend-Anwendungen, die besonders gut zu React passt. Sie beantwortet vor allem eine Frage:

Wie strukturiere ich eine wachsende Frontend-Anwendung so, dass Komponenten, Geschäftslogik und Features nicht zu einem schwer wartbaren Abhängigkeitsnetz werden?

Der entscheidende Punkt: FSD strukturiert den Code nach fachlicher Verantwortung und Bedeutung, nicht primär nach technischen Dateitypen.

Das Grundprinzip

Eine klassische React-Struktur sieht häufig so aus:

src/
├── components/
├── hooks/
├── services/
├── utils/
├── pages/
└── types/

Das funktioniert bei kleinen Anwendungen gut. Mit wachsender Anwendung entstehen aber schnell Probleme:

components/
  Button/
  UserCard/
  ProductCard/
  LoginForm/

hooks/
  useUser/
  useProduct/
  useCart/
  useAuth/

services/
  userService/
  productService/
  cartService/

Die fachliche Zusammengehörigkeit wird auseinandergerissen.

FSD organisiert stattdessen beispielsweise so:

src/
├── app/
├── pages/
├── widgets/
├── features/
├── entities/
└── shared/

Innerhalb dieser Ebenen wird wiederum fachlich geschnitten.

Die sechs Ebenen von Feature-Sliced Design

FSD definiert sechs sogenannte Layers:

app
pages
widgets
features
entities
shared

Ihre wichtigste Eigenschaft ist die Richtung der Abhängigkeiten:

app
 ↓
pages
 ↓
widgets
 ↓
features
 ↓
entities
 ↓
shared

Eine höherliegende Schicht darf eine niedrigere verwenden. Eine niedrigere Schicht sollte dagegen nicht von einer höheren abhängen. Das ist eines der wichtigsten Prinzipien von FSD.

Die sechs Layer im Detail

app

Enthält das anwendungsweite Setup – Provider, Routing, globale Styles, den Einstiegspunkt der Anwendung. Hier befinden sich beispielsweise:

app/
├── providers/
├── router/
├── styles/
└── config/

Typische Dinge:

QueryClientProvider
AuthProvider
Router
ThemeProvider
Global CSS
ErrorBoundary

pages

Entspricht konkreten Seiten oder Routen, etwa einer Produktdetailseite oder einer Checkout-Seite. Eine Page sollte möglichst wenig eigene fachliche Logik enthalten und stattdessen Widgets und Features zusammensetzen. Zum Beispiel:

pages/
├── home/
├── products/
├── product-details/
├── checkout/
└── profile/

Eine Product-Page könnte beispielsweise so aussehen:

pages/product-details/
└── ui/
    └── ProductDetailsPage.tsx

Sie komponiert:

ProductDetailsPage
    │
    ├── ProductInformation
    │
    ├── AddToCart
    │
    └── RelatedProducts

widgets

Enthält größere, eigenständige UI-Bereiche wie einen Header, eine Sidebar oder eine Produktliste. Ein Widget kombiniert typischerweise mehrere Entities und Features zu einem sichtbaren Block. Zum Beispiel:

widgets/
├── Header/
├── Sidebar/
├── ProductList/
├── ShoppingCart/
└── UserProfile/

Ein Widget kombiniert häufig mehrere Entities und Features. Beispielsweise:

widgets/ProductList/

könnte verwenden:

entities/product
features/add-to-cart
features/toggle-favorite

Dadurch entsteht eine Art Kompositionsschicht.

features

Bildet konkrete Benutzerinteraktionen mit fachlichem Wert ab, etwa das Hinzufügen eines Produkts zum Warenkorb, eine Passwortänderung oder eine Produktsuche. Beispiele:

features/
├── auth/
├── add-to-cart/
├── search-product/
├── change-password/
├── edit-profile/
└── toggle-favorite/

Beispielsweise:

features/add-to-cart/
├── ui/
├── model/
└── api/

Die Feature könnte verwenden:

features/add-to-cart
        ↓
entities/product
        ↓
shared/ui

entities

Enthält fachlich relevante Objekte der Anwendung, etwa Nutzer, Produkt oder Bestellung, inklusive ihrer Darstellung und ihres Zugriffs. Beispielsweise in einer E-Commerce-Anwendung:

entities/
├── user/
├── product/
├── order/
└── cart/

Eine Entity product könnte enthalten:

entities/product/
├── ui/
├── model/
├── api/
└── lib/

Zum Beispiel:

entities/product/ui/ProductCard.tsx
entities/product/api/getProduct.ts
entities/product/model/types.ts

shared

Enthält wiederverwendbaren, fachlich neutralen Code – UI-Bausteine, Hilfsfunktionen, API-Client-Setup, Konfiguration.

Zum Beispiel:

shared/
├── ui/
├── api/
├── lib/
├── config/
└── assets/

shared ist nicht einfach dein alter utils/-Ordner.

Ein guter Test ist: Könnte ich diesen Code problemlos in eine völlig andere Anwendung übernehmen? Wenn ja, ist shared wahrscheinlich sinnvoll.

Slices und Segments – das eigentliche Herzstück

Slices, die fachliche Einheit

Die sechs Layer allein erklären noch nicht, wie FSD wirklich funktioniert. Innerhalb jeder Schicht – außer app und shared – wird zusätzlich nach Slices unterteilt. Ein Slice ist eine fachliche Einheit, etwa entities/product, entities/user oder features/order. Ein Slice kapselt alles, was zu einem fachlichen Konzept gehört, statt es wie in der klassischen Struktur über api/products.ts, types/products.ts, hooks/useProducts.ts und components/ProductCard.tsx zu verstreuen. Beispielsweise:

entities/
├── user/
├── product/
└── order/

Hier sind user, product und order die Slices.

Bei Features:

features/
├── add-to-cart/
├── checkout/
├── login/
└── search-products/

Auch das sind Slices.

Ein Slice kapselt alles, was zu einem fachlichen Konzept gehört.

Segments, die technische Kategorien innerhalb eines Slice

Innerhalb eines Slice gibt es wiederum Segments, das sind technische Kategorien. Zum Beispiel:

entities/product/
├── ui/
├── api/
├── model/
└── lib/

Typische Segments sind:

Segment

Inhalt

ui

React-Komponenten

api

API-Zugriff

model

State, Types, Business Logic

lib

Hilfsfunktionen

config

Konfiguration

Damit ergibt sich eine klare Hierarchie: Layer, darunter Slice, darunter Segment, darunter das einzelne Modul.

Das Public-API-Prinzip

Ein zentraler Bestandteil von FSD ist die sogenannte Public API eines Slice. Jeder Slice exponiert seine Inhalte über eine zentrale index-Datei. Andere Teile der Anwendung importieren niemals direkt aus internen Pfaden eines Slice, sondern ausschließlich über diese zentrale Datei.

Ein Beispiel: Statt formatPrice direkt aus entities/product/lib/formatPrice zu importieren, importiert man es aus entities/product – also über die Public API des Slice. Das mag zunächst wie eine Kleinigkeit wirken, hat aber einen großen Effekt: Die interne Struktur eines Slice bleibt privat und kann jederzeit umgebaut werden, ohne dass an anderer Stelle in der Anwendung etwas bricht. Das ist konzeptionell vergleichbar mit der öffentlichen Schnittstelle eines Softwaremoduls oder Pakets.

Drei typische FSD-Fehler in der Praxis

1. "Shared" wird zur Müllhalde
Der häufigste Fehler: Am Ende landet alles in shared/utils, shared/helpers, shared/common – und man ist strukturell wieder bei einem klassischen utils/-Ordner. Der entscheidende Test dafür, ob etwas wirklich nach shared gehört: "Könnte ich diesen Code problemlos in eine komplett andere Anwendung übernehmen?" Shared heißt nicht "wird an mehreren Stellen verwendet", sondern "fachlich unabhängig".

2. Alles wird zur Feature
show-user, display-product, format-price sind keine Features – das sind technische Hilfsfunktionen. Eine echte Feature bildet eine konkrete Nutzerinteraktion mit Business-Wert ab: add-to-cart, change-password, search-products. Die Testfrage: "Was kann der Benutzer hier konkret tun?"

3. FSD wird mit einer Ordnerstruktur verwechselt
Man kann eine "perfekte" FSD-Struktur haben und trotzdem schlecht geschnittene Slices. FSD ist primär eine Denkweise über fachliche Verantwortlichkeit und Abhängigkeitsrichtung – nicht nur ein Verzeichnis-Schema.

Vor- und Nachteile

Vorteile

  • Klare, vorhersehbare Struktur, gerade bei mehreren Teams/Devs

  • Verhindert "Spaghetti-Imports" durch die strikte Import-Regel

  • Fördert fachliche statt technische Modularisierung

  • Gute Skalierbarkeit bei großen Codebasen

Nachteile / Kritik

  • Overhead und Boilerplate bei kleinen/mittleren Projekten – die Layer-Hierarchie fühlt sich schnell übertrieben an

  • Manche Slice-vs-Layer-Entscheidungen sind in der Praxis nicht eindeutig (ist etwas ein Widget oder eine Page?)

  • processes wurde in FSD 2.0 quasi verworfen, was zeigt, dass das Modell selbst noch in Bewegung ist

  • Tooling (ESLint-Plugin eslint-plugin-boundaries oder das offizielle @feature-sliced/eslint-config) ist nötig, um die Regeln wirklich durchzusetzen – ohne Linting verkommt die Struktur schnell zur Konvention auf dem Papier

Das mentale Modell

Ich würde FSD nicht primär als diese Ordnerstruktur lernen:

app
pages
widgets
features
entities
shared

Sondern als drei Fragen:

1. Was ist fachlich?

User
Product
Order
Food
Meal

→ Entities

2. Was kann der Benutzer tun?

Login
AddToCart
Checkout
Search
EditProfile

→ Features

3. Wie wird daraus eine Oberfläche?

Header
ProductList
ShoppingCart
Dashboard

→ Widgets / Pages

Und darunter:

shared

für wirklich fachlich unabhängige Bausteine.

Einordnung gegenüber anderen Ansätzen

  • FSD vs. DDD: DDD beantwortet welche fachlichen Grenzen die Anwendung hat; FSD beantwortet, wie man den Frontend-Code entlang dieser Grenzen organisiert. Beides lässt sich kombinieren (DDD liefert die Domänenlogik, FSD die Code-Struktur).

  • FSD vs. Clean Architecture: Clean Architecture fokussiert auf Dependency Inversion und Trennung von Business-Logik/Infrastruktur; FSD auf Features/Domains/UI-Struktur. Auch kombinierbar, z. B. Clean-Architecture-Prinzipien innerhalb eines model/-Segments.

  • FSD vs. Next.js App Router: Namenskollision – Next.js' app/-Verzeichnis (Routing) und FSD-Layer app (Application-Infrastruktur wie Provider) sind nicht dasselbe. Erfordert bewusste Entscheidung, wie man beides verzahnt.

  • FSD vs. Container/Presentational Components: FSD ersetzt dieses Muster tendenziell durch fachliche Kapselung (z. B. Datenbeschaffung direkt im Entity-Slice statt über einen separaten Container).

Wann sich FSD lohnt – und wann nicht

FSD wird interessant, wenn eine Anwendung viele Features besitzt, von mehreren Entwicklern über einen längeren Zeitraum weiterentwickelt wird, komplexe Businesslogik enthält, viele React-Komponenten umfasst, unterschiedliche fachliche Bereiche abdeckt und regelmäßig erweitert wird. Für eine größere SaaS-Anwendung mit wachsendem Team ist FSD oft eine sehr passende Wahl.

Bei einer Landingpage, einem Portfolio, einem kleinen Blog, einer einfachen CRUD-Anwendung, einem Prototyp oder einer kleinen internen Anwendung erzeugt FSD dagegen häufig unnötigen Overhead. Dort reicht in aller Regel eine klassische, flache Struktur mit components, hooks, lib, pages und services vollkommen aus. Ein guter Grundsatz lautet entsprechend, dass die Architektur mit der Komplexität der Anwendung wachsen sollte – und nicht ihr vorauseilen muss.

Fazit

Wer aus diesem Artikel nur einen Gedanken mitnehmen möchte, sollte diesen wählen: Strukturiere eine React-Anwendung nach fachlicher Verantwortung und kontrolliere konsequent die Richtung der Abhängigkeiten. Nicht die Frage, wo eine Komponente, ein Hook oder ein Service liegt, sollte im Vordergrund stehen, sondern die Frage, zu welchem fachlichen Konzept ein Stück Code gehört, wer es verwenden darf, welche Abhängigkeiten es eingehen darf und welche Teile seiner Implementierung privat bleiben sollen.

Genau darin liegt auch die Stärke von FSD im Zusammenspiel mit anderen Ansätzen wie Domain-Driven Design, Clean Architecture oder modernem Server-State-Management: Es ist keine konkurrierende, sondern eine ergänzende Methodik – eine, die sich lohnt, sobald ein Projekt tatsächlich an die Grenzen einer klassischen, technisch organisierten Struktur stößt.