Files
main_frontend/.agents/skills/feature-sliced-design/references/framework-integration.md
T
Daniil 21e936a827
dev / deploy (push) Successful in 2m15s
compute / deploy (push) Has been cancelled
chore: agentic upgrade
2026-05-17 02:11:33 +03:00

14 KiB

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 @/<layer>/* 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

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

// app/layout.tsx
import { Providers } from '@/app/providers';
import '@/app/styles/globals.css';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body><Providers>{children}</Providers></body>
    </html>
  );
}

// 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/:

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
// 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:

// 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:

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

// 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:

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
// 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";
// 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

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

<!-- pages/index.vue: thin route entry -->
<template>
  <HomePage />
</template>
<script setup>
import { HomePage } from "@/pages/home";
</script>

Path aliases

In addition to the standard tsconfig.json mapping, Nuxt requires explicit runtime aliases in nuxt.config.ts:

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

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:

// 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:

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

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:

// src/pages/index.astro
import { HomePage } from '@/_pages/home';
<HomePage />

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:

{
  "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:

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.