# Framework Integration How to set up FSD within specific frameworks. Covers directory placement, routing integration, and framework-specific path alias configuration. ## General Principle Place FSD layers inside `src/` to avoid naming conflicts with framework directories. The FSD `app/` and `pages/` layers are **not** the same as framework directories with the same names (e.g., Next.js `app/`). All FSD projects follow the same `@//*` path alias convention. The exact configuration differs by framework. See each framework section below. Astro is the one exception, using a single `@/*` alias instead. ## Next.js FSD is compatible with both the App Router and the Pages Router. The main conflict is that Next.js owns the `app/` and `pages/` folder names, and both collide with FSD layer names. Resolve the conflict by moving the Next.js routing folders to the project root and importing FSD pages from `src/` into them. ### App Router The Next.js `app/` folder lives at the project root. **Always create an empty `pages/` folder at the project root as well, even when you only use the App Router.** Without it, Next.js tries to use `src/pages/` as the Pages Router and the build breaks. Add a `pages/README.md` explaining why the folder exists. #### Directory structure ```text my-nextjs-project/ app/ ← Next.js App Router (routing only) layout.tsx page.tsx profile/ page.tsx api/ get-example/ route.ts pages/ ← Empty, required even for App Router README.md src/ app/ ← FSD app layer providers/ index.tsx ← All providers (QueryClient, theme, etc.) styles/ globals.css api-routes/ ← Route Handler implementations (see below) index.ts get-example-data.ts pages/ ← FSD pages layer home/ ui/HomePage.tsx index.ts profile/ ui/ProfilePage.tsx model/profile.ts api/fetch-profile.ts index.ts widgets/ ← FSD widgets layer (when needed) features/ ← FSD features layer (when needed) entities/ ← FSD entities layer (when needed) shared/ ← FSD shared layer ui/ lib/ api/ db/ ← Database queries (see below) ``` #### Wiring Next.js routes to FSD pages ```typescript // app/layout.tsx import { Providers } from '@/app/providers'; import '@/app/styles/globals.css'; export default function RootLayout({ children }) { return ( {children} ); } // app/example/page.tsx: re-export the FSD page (component + metadata) export { ExamplePage as default, metadata } from '@/pages/example'; ``` Always re-export both the component and `metadata`. Route files contain no logic. ### Pages Router The Pages Router uses `pages/` at the project root. The FSD `src/` tree is unchanged. Routes re-export from `src/pages/`: ```text my-nextjs-project/ pages/ ← Next.js Pages Router (routing only) _app.tsx api/example.ts ← API route re-export example/index.tsx src/ app/ custom-app/ ← Custom App component api-routes/ ← Route Handler implementations pages/ example/ ui/example.tsx index.ts ``` ```typescript // pages/example/index.tsx export { Example as default } from '@/pages/example'; // pages/_app.tsx: re-export the custom App from src/app/custom-app export { App as default } from '@/app/custom-app'; ``` The custom App component itself lives in `src/app/custom-app/` and exports `App` from its public API like any other FSD slice. ### Middleware and instrumentation `middleware.js` and `instrumentation.js` must live at the **project root**, next to the Next.js `app/` and `pages/` folders. Next.js will not detect them inside `src/`. ### Route Handlers (API routes) Use a dedicated `api-routes` segment in the FSD `app/` layer (`src/app/api-routes/`) to host the actual request handlers. The Next.js `app/api/*/route.ts` (App Router) or `pages/api/*.ts` (Pages Router) files become thin re-exports. **App Router:** ```typescript // src/app/api-routes/get-example-data.ts import { getExamplesList } from '@/shared/db'; export const getExampleData = () => { try { const examplesList = getExamplesList(); return Response.json({ examplesList }); } catch { return Response.json(null, { status: 500, statusText: 'Ouch, something went wrong', }); } }; // src/app/api-routes/index.ts export { getExampleData } from './get-example-data'; // app/api/example/route.ts export { getExampleData as GET } from '@/app/api-routes'; ``` **Pages Router:** ```typescript // src/app/api-routes/get-example-data.ts import type { NextApiRequest, NextApiResponse } from 'next'; const config = { api: { bodyParser: { sizeLimit: '1mb' } }, maxDuration: 5 }; const handler = (req: NextApiRequest, res: NextApiResponse) => res.status(200).json({ message: 'Hello from FSD' }); export const getExampleData = { config, handler } as const; // app/api/example.ts import { getExampleData } from '@/app/api-routes'; export const config = getExampleData.config; export default getExampleData.handler; ``` FSD is primarily a frontend methodology. If `api-routes` grows to many endpoints, consider moving the backend to a separate package in a monorepo. ### Database access Place database queries in a `db` segment in `shared/` (`src/shared/db/`). Co-locate caching and revalidation logic with the queries themselves. ### Path aliases ```json // tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/app/*": ["src/app/*"], "@/pages/*": ["src/pages/*"], "@/widgets/*": ["src/widgets/*"], "@/features/*": ["src/features/*"], "@/entities/*": ["src/entities/*"], "@/shared/*": ["src/shared/*"] } } } ``` Next.js reads `tsconfig.json` paths automatically. No `next.config.js` alias configuration is needed. ### Server Components and Public API splitting FSD layers work inside both Server and Client Components. However, the standard single `index.ts` public API can cause problems in RSC environments because re-exporting client and server code from the same entry point may trigger bundler errors or unintended boundary crossings. Split the public API into multiple entry points per environment: ```text entities/user/ model/ user.ts ui/ UserAvatar.tsx ← 'use client', uses hooks UserProfileCard.tsx ← Server Component, no hooks api/ user-queries.server.ts ← Server-only data fetching index.ts ← Shared exports (types, pure functions) index.client.ts ← Client component exports index.server.ts ← Server component + server-only exports ``` ```typescript // entities/user/index.ts: shared (types, pure logic, no components) export type { User } from "./model/user"; export { formatUserName } from "./model/user"; // entities/user/index.client.ts: client components only export { UserAvatar } from "./ui/UserAvatar"; // entities/user/index.server.ts: server components + server-only code export { UserProfileCard } from "./ui/UserProfileCard"; export { fetchUser } from "./api/user-queries.server"; ``` ```typescript // Consumers import from the appropriate entry point: // In a Server Component (pages/profile/ui/ProfilePage.tsx) import { UserProfileCard } from "@/entities/user/index.server"; import type { User } from "@/entities/user"; // In a Client Component (features/comment/ui/CommentAuthor.tsx) import { UserAvatar } from "@/entities/user/index.client"; ``` **Rules for split public APIs:** 1. **`index.ts`**: types, constants, and pure functions that work in both environments. Default import path. 2. **`index.client.ts`**: components using `'use client'`, hooks, or browser APIs. 3. **`index.server.ts`**: Server Components and server-only data fetching. 4. The `index.[env].ts` pattern generalises beyond RSC. Any meta-framework with distinct runtime environments can use it (e.g., `index.edge.ts`). Verified for Next.js App Router; Nuxt and Astro compatibility is under review. 5. Steiger support for multiple entry points is available or coming in an upcoming release. If Steiger flags these files, check for version updates. **When NOT to split:** A slice with no client/server boundary concerns uses a single `index.ts`. Split only when a slice actually has both client and server exports. ## Nuxt 3 ### Directory structure ```text my-nuxt-project/ pages/ ← Nuxt file-based routing index.vue ← Route entry, imports from FSD pages layer profile.vue src/ app/ ← FSD app layer providers/ pages/ ← FSD pages layer home/ ui/HomePage.vue index.ts profile/ ui/ProfilePage.vue model/profile.ts index.ts shared/ ← FSD shared layer ui/ lib/ api/ ``` ### Wiring Nuxt routes to FSD pages ```vue ``` ### Path aliases In addition to the standard `tsconfig.json` mapping, Nuxt requires explicit runtime aliases in `nuxt.config.ts`: ```typescript // nuxt.config.ts import { resolve } from "path"; export default defineNuxtConfig({ alias: { "@/app": resolve(__dirname, "src/app"), "@/pages": resolve(__dirname, "src/pages"), "@/widgets": resolve(__dirname, "src/widgets"), "@/features": resolve(__dirname, "src/features"), "@/entities": resolve(__dirname, "src/entities"), "@/shared": resolve(__dirname, "src/shared"), }, }); ``` ## Vite + React ### Directory structure ```text my-vite-project/ src/ app/ ← FSD app layer providers/ router.tsx styles/ main.tsx ← Entry point pages/ shared/ index.html vite.config.ts tsconfig.json ``` ### Path aliases Mirror the standard `tsconfig.json` mapping in `vite.config.ts` so the Vite resolver agrees with TypeScript: ```typescript // vite.config.ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { resolve } from "path"; export default defineConfig({ plugins: [react()], resolve: { alias: { "@/app": resolve(__dirname, "src/app"), "@/pages": resolve(__dirname, "src/pages"), "@/widgets": resolve(__dirname, "src/widgets"), "@/features": resolve(__dirname, "src/features"), "@/entities": resolve(__dirname, "src/entities"), "@/shared": resolve(__dirname, "src/shared"), }, }, }); ``` ## Create React App (CRA) CRA is no longer actively maintained. **Migrate to Vite for new projects.** If you must stay on CRA, path aliases require ejecting or using `craco` to override the webpack config: ```javascript // craco.config.js const path = require("path"); module.exports = { webpack: { alias: { "@/app": path.resolve(__dirname, "src/app"), "@/pages": path.resolve(__dirname, "src/pages"), "@/widgets": path.resolve(__dirname, "src/widgets"), "@/features": path.resolve(__dirname, "src/features"), "@/entities": path.resolve(__dirname, "src/entities"), "@/shared": path.resolve(__dirname, "src/shared"), }, }, }; ``` ## Astro Astro uses `src/pages/` for file-based routing, which collides with the FSD `pages/` layer. Move the FSD pages layer to `src/_pages/` (with the underscore prefix) and reserve `src/pages/` for Astro routes. ### Directory structure ```text my-astro-project/ src/ pages/ ← Astro routing (thin entry points) 404.astro index.astro _pages/ ← FSD pages layer home/ ui/HomePage.astro index.ts widgets/ features/ entities/ shared/ ``` ### Wiring Astro routes to FSD pages The Astro route file imports and renders the FSD page, nothing else: ```astro // src/pages/index.astro import { HomePage } from '@/_pages/home'; ``` ### Path aliases (tsconfig.json) Astro projects use a single `@/*` alias instead of one alias per layer. This is the convention the FSD Astro guide recommends: ```json { "extends": "astro/tsconfigs/strict", "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } ``` Imports then reference the layer path directly: `@/_pages/home`, `@/shared/ui`, `@/entities/user`. ### Working with integrations Some Astro integrations (for example, Starlight) use content collections that expect content in fixed folders such as `src/content/docs/`. If the integration does not allow the path to be changed, leave it as-is. The content folder lives alongside FSD layers without collision: ```text src/ _pages/ ← FSD pages layer content/ ← Integration content (Starlight, etc.) docs/ getting-started.md shared/ ← FSD shared layer ``` Let the integration handle its own routing and rendering, while FSD layers manage application-specific code. ## Key Reminders for All Frameworks 1. **FSD lives in `src/`**: root-level `app/` and `pages/` belong to the framework's routing, not FSD. 2. **Framework route files are thin wrappers**: they import and render FSD page components. Business logic stays in FSD pages. 3. **Path aliases are required**: configure both the bundler and `tsconfig.json`. 4. **Pages First still applies**: regardless of framework, start with code in FSD `pages/` and extract only when needed.