jpskill.com
🛠️ 開発・MCP コミュニティ

nuxt-fsd

Nuxt 4+プロジェクトで、Feature-Sliced Design(FSD)アーキテクチャに基づいたコード配置場所やモジュール構造の決定、Slice間の連携、新規ページ作成などを支援し、FSDに沿った開発を効率化するSkill。

📜 元の英語説明(参考)

Feature-Sliced Design (FSD) architecture for Nuxt 4+ projects. Use when deciding where to place new code, which layer a module belongs to, how to structure slices and segments, handle cross-slice communication, or when scaffolding new Nuxt pages/features/entities. Covers layer mapping to Nuxt conventions, the thin-page routing pattern, `src/` as FSD root, auto-import strategy, composable patterns, and server-side considerations. Activates on mentions of FSD, feature-sliced, layers, slices, architecture decisions, or when creating new modules in a Nuxt project that follows FSD structure.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Nuxt 4+プロジェクトで、Feature-Sliced Design(FSD)アーキテクチャに基づいたコード配置場所やモジュール構造の決定、Slice間の連携、新規ページ作成などを支援し、FSDに沿った開発を効率化するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o nuxt-fsd.zip https://jpskill.com/download/9300.zip && unzip -o nuxt-fsd.zip && rm nuxt-fsd.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9300.zip -OutFile "$d\nuxt-fsd.zip"; Expand-Archive "$d\nuxt-fsd.zip" -DestinationPath $d -Force; ri "$d\nuxt-fsd.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して nuxt-fsd.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → nuxt-fsd フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Nuxt 4+ 向け Feature-Sliced Design

公式 FSD ドキュメント: https://feature-sliced.design/ LLM リファレンス: https://feature-sliced.design/llms-full.txt

コア原則

FSD はコードをレイヤーに編成し、厳格な依存関係ルールを設けます。各レイヤーは、下のレイヤーからのみインポートでき、上や横のレイヤーからは決してインポートできません。これにより、複雑な依存関係を防ぎます。ウィジェットはそれを使用するページについて知らず、フィーチャーは別のフィーチャーにアクセスせず、エンティティはそれを表示する UI に依存しません。

FSD は src/ 内でのみ存在します。Nuxt の app/ ディレクトリはランタイムシェルであり、src/ から FSD コードを使用しますが、それ自体は FSD によって編成されていません。app/composables/app/middleware/app/plugins/app/layouts/ などは、FSD レイヤーではなく、Nuxt の規約に従います。

FSD レイヤー (すべて src/ 内)

レイヤーは、上 (最も特定的) から下 (最も安定) の順に並べられています。すべてのレイヤーは下のレイヤーからのみインポートできます

pages      ← src/pages/ 内の FSD ページスライス — ウィジェット、フィーチャー、エンティティを構成します
widgets    ← ロジック + プレゼンテーションが結合された自己完結型の UI ブロック
features   ← 再利用可能なユーザーインタラクション — ロジックはスタンドアロンで、UI は置き換え可能です
entities   ← ビジネスドメインモデル: 型、スキーマ、ベースクエリ、フォーマッタ
shared     ← フレームワークユーティリティ、UI キット、API クライアント、ヘルパー — ビジネスロジックはゼロ

レイヤーのクイックリファレンス

レイヤー 含むもの 含まないもの
shared UI キットコンポーネント、cn()、日付/文字列ヘルパー、API クライアント設定、型ユーティリティ ビジネスロジック、ドメイン概念
entities UserProductOrder 型、Zod スキーマ、ベース useFetch ラッパー、モデルフォーマッタ ユーザーアクション、インタラクティブなフィーチャー
features 認証フロー、検索ロジック、カート操作、フォーム送信コンポーザブル エンティティ定義、レイアウトに関する懸念
widgets ProductCardAppHeaderSidebar、独自のデータを持つ完全に構成されたセクション 生のエンティティデータアクセス、ルートロジック
pages ウィジェット/フィーチャーを組み立てるページスライス、ページ固有のデータフェッチ 再利用可能なピース (証明された場合に抽出)

Nuxt 4+ ディレクトリマッピング

project-root/
├── app/                          ← Nuxt 4 アプリケーションディレクトリ (ランタイムシェル、FSD ではない)
│   ├── app.vue                   ← ルートコンポーネント
│   ├── pages/                    ← 薄いルーティングシェル ("Thin page pattern" を参照)
│   │   ├── index.vue
│   │   └── products/
│   │       ├── index.vue
│   │       └── [id].vue
│   ├── layouts/                  ← Nuxt レイアウト
│   │   └── default.vue
│   ├── plugins/                  ← Nuxt プラグイン
│   ├── middleware/               ← Nuxt ルートミドルウェア
│   └── composables/              ← Nuxt グローバルコンポーザブル
├── src/                          ← FSD ルート — すべてのスライスされたレイヤーがここに存在します
│   ├── pages/                    ← FSD ページスライス (完全な実装)
│   │   ├── product-detail/
│   │   │   ├── ui/
│   │   │   │   └── ProductDetailPage.vue
│   │   │   ├── model/
│   │   │   │   └── useProductDetail.ts
│   │   │   └── index.ts
│   │   └── (checkout)/           ← ルートグループ (括弧)
│   │       ├── _layout/          ← サブルートの共有レイアウト
│   │       │   └── CheckoutLayout.vue
│   │       ├── cart/
│   │       │   ├── ui/
│   │       │   └── index.ts
│   │       └── payment/
│   │           ├── ui/
│   │           └── index.ts
│   ├── widgets/                  ← FSD ウィジェットレイヤー
│   │   └── product-card/
│   │       ├── ui/
│   │       │   └── ProductCard.vue
│   │       ├── model/
│   │       │   └── useProductCard.ts
│   │       └── index.ts
│   ├── features/                 ← FSD フィーチャーレイヤー
│   │   └── add-to-cart/
│   │       ├── ui/
│   │       │   └── AddToCartButton.vue
│   │       ├── model/
│   │       │   └── useAddToCart.ts
│   │       ├── api/
│   │       │   └── mutations.ts
│   │       └── index.ts
│   ├── entities/                 ← FSD エンティティレイヤー
│   │   └── product/
│   │       ├── ui/
│   │       │   └── ProductPreview.vue
│   │       ├── model/
│   │       │   ├── types.ts
│   │       │   └── schema.ts
│   │       ├── api/
│   │       │   └── queries.ts
│   │       └── index.ts
│   └── shared/                   ← FSD 共有レイヤー
│       ├── ui/
│       │   ├── UiButton.vue
│       │   └── UiModal.vue
│       ├── lib/
│       │   ├── format-date.ts
│       │   └── cn.ts
│       ├── api/
│       │   └── client.ts
│       └── config/
│           └── constants.ts
├── server/                       ← Nuxt サーバールート (FSD クライアントレイヤーの外側)
├── public/                       ← 静的アセット
└── nuxt.config.ts

主要なマッピングルール

  1. src/FSD ルートです。すべての FSD レイヤー (pages/widgets/features/entities/shared/) がここに存在します。
  2. app/Nuxt ランタイムシェルです。FSD ではなく、Nuxt の規約に従います。インポートを介して src/ から FSD コードを使用します。
  3. app/pages/ には 薄いルーティングシェルのみが含まれます。src/pages/ ページスライスに委譲します。"Thin page pattern" を参照してください。
  4. server/ は FSD の外側にあります。サーバールートは Nuxt サーバーの規約に従います。

Thin page pattern

app/pages/*.vue ファイルは ルーティングシェル です (5〜25 行)。これらは Nuxt のファイルベースルーティングのためだけに存在し、すべての実際の実装を src/pages/ の FSD ページスライスに委譲します。

薄いページ:

  • src/pages/<slice> からページコンポーネントをインポートします
  • i18n パスマッピングのために definePageMeta({ i18n: { ... } }) を定義します (もし @nuxtjs/i18ncustomRoutes: 'meta' で使用している場合)
  • バリデーション、レイアウト、ルートキーのために definePageMeta() を定義します
  • インポートされたページコンポーネントをレンダリングします

薄いページの例


<!-- app/pages/products/[id].vue — 薄いルーティングシェル -->
<script setup lang="ts">
import { ProductDetailPage } from '~~/src/pages/product-detail'

definePageMeta({
  layout: 'default',
  validate: async (route) => /^\d+$/.test(route.params.id as string),
  i18n: {
    paths: {
      cs: '/produkty/[id]',
      en: '/products/[id]',
    },
  },
})
</script>

<templat
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Feature-Sliced Design for Nuxt 4+

Official FSD docs: https://feature-sliced.design/ LLM reference: https://feature-sliced.design/llms-full.txt

Core principle

FSD organizes code into layers with a strict dependency rule: each layer can only import from layers below it, never above or sideways. This prevents tangled dependencies — a widget never knows about a page that uses it, a feature never reaches into another feature, and an entity never depends on the UI that displays it.

FSD lives exclusively in src/. The Nuxt app/ directory is the runtime shell — it consumes FSD code from src/ but is not itself organized by FSD. Things like app/composables/, app/middleware/, app/plugins/, and app/layouts/ follow Nuxt conventions, not FSD layers.

FSD layers (all in src/)

Layers are ordered top (most specific) to bottom (most stable). Every layer can only import from layers below it.

pages      ← FSD page slices in src/pages/ — compose widgets, features, entities
widgets    ← Self-contained UI blocks with coupled logic + presentation
features   ← Reusable user interactions — logic is standalone, UI is replaceable
entities   ← Business domain models: types, schemas, base queries, formatters
shared     ← Framework utilities, UI kit, API client, helpers — zero business logic

Layer quick-reference

Layer Contains Does NOT contain
shared UI kit components, cn(), date/string helpers, API client setup, type utilities Business logic, domain concepts
entities User, Product, Order types, Zod schemas, base useFetch wrappers, model formatters User actions, interactive features
features Auth flow, search logic, cart operations, form submission composables Entity definitions, layout concerns
widgets ProductCard, AppHeader, Sidebar, complete composed sections with own data Raw entity data access, route logic
pages Page slices assembling widgets/features, page-specific data fetching Reusable pieces (extract when proven)

Nuxt 4+ directory mapping

project-root/
├── app/                          ← Nuxt 4 app directory (runtime shell, NOT FSD)
│   ├── app.vue                   ← Root component
│   ├── pages/                    ← Thin routing shells (see "Thin page pattern")
│   │   ├── index.vue
│   │   └── products/
│   │       ├── index.vue
│   │       └── [id].vue
│   ├── layouts/                  ← Nuxt layouts
│   │   └── default.vue
│   ├── plugins/                  ← Nuxt plugins
│   ├── middleware/               ← Nuxt route middleware
│   └── composables/              ← Nuxt global composables
├── src/                          ← FSD root — all sliced layers live here
│   ├── pages/                    ← FSD page slices (full implementations)
│   │   ├── product-detail/
│   │   │   ├── ui/
│   │   │   │   └── ProductDetailPage.vue
│   │   │   ├── model/
│   │   │   │   └── useProductDetail.ts
│   │   │   └── index.ts
│   │   └── (checkout)/           ← Route group (parentheses)
│   │       ├── _layout/          ← Shared layout for sub-routes
│   │       │   └── CheckoutLayout.vue
│   │       ├── cart/
│   │       │   ├── ui/
│   │       │   └── index.ts
│   │       └── payment/
│   │           ├── ui/
│   │           └── index.ts
│   ├── widgets/                  ← FSD widgets layer
│   │   └── product-card/
│   │       ├── ui/
│   │       │   └── ProductCard.vue
│   │       ├── model/
│   │       │   └── useProductCard.ts
│   │       └── index.ts
│   ├── features/                 ← FSD features layer
│   │   └── add-to-cart/
│   │       ├── ui/
│   │       │   └── AddToCartButton.vue
│   │       ├── model/
│   │       │   └── useAddToCart.ts
│   │       ├── api/
│   │       │   └── mutations.ts
│   │       └── index.ts
│   ├── entities/                 ← FSD entities layer
│   │   └── product/
│   │       ├── ui/
│   │       │   └── ProductPreview.vue
│   │       ├── model/
│   │       │   ├── types.ts
│   │       │   └── schema.ts
│   │       ├── api/
│   │       │   └── queries.ts
│   │       └── index.ts
│   └── shared/                   ← FSD shared layer
│       ├── ui/
│       │   ├── UiButton.vue
│       │   └── UiModal.vue
│       ├── lib/
│       │   ├── format-date.ts
│       │   └── cn.ts
│       ├── api/
│       │   └── client.ts
│       └── config/
│           └── constants.ts
├── server/                       ← Nuxt server routes (outside FSD client layers)
├── public/                       ← Static assets
└── nuxt.config.ts

Key mapping rules

  1. src/ is the FSD root. All FSD layers (pages/, widgets/, features/, entities/, shared/) live here.
  2. app/ is the Nuxt runtime shell. It follows Nuxt conventions, not FSD. It consumes FSD code from src/ via imports.
  3. app/pages/ contains thin routing shells only — they delegate to src/pages/ page slices. See "Thin page pattern" below.
  4. server/ is outside FSD entirely. Server routes follow Nuxt server conventions.

Thin page pattern

app/pages/*.vue files are routing shells (5–25 lines). They exist solely for Nuxt's file-based routing and delegate all real implementation to FSD page slices in src/pages/.

A thin page:

  • Imports the page component from src/pages/<slice>
  • Defines definePageMeta({ i18n: { ... } }) for i18n path mappings (if using @nuxtjs/i18n with customRoutes: 'meta')
  • Defines definePageMeta() for validation, layout, route key
  • Renders the imported page component

Example thin page

<!-- app/pages/products/[id].vue — thin routing shell -->
<script setup lang="ts">
import { ProductDetailPage } from '~~/src/pages/product-detail'

definePageMeta({
  layout: 'default',
  validate: async (route) => /^\d+$/.test(route.params.id as string),
  i18n: {
    paths: {
      cs: '/produkty/[id]',
      en: '/products/[id]',
    },
  },
})
</script>

<template>
  <ProductDetailPage />
</template>

The actual implementation lives in the FSD page slice:

src/pages/product-detail/
  ui/
    ProductDetailPage.vue    ← Full page implementation
  model/
    useProductDetail.ts
  index.ts                   ← Exports ProductDetailPage

FSD page slices in src/pages/

src/pages/ contains full FSD page slices — not Nuxt file-based routes. These are regular FSD slices with ui/, model/, api/, and index.ts.

Conventions

  • Slice naming: kebab-case matching the domain concept: product-detail/, blog-article-detail/, user-profile/
  • Route groups: Parentheses group related sub-routes: (checkout)/cart/, (checkout)/payment/
  • Shared layout: _layout/ inside a route group holds layout components shared across sub-routes

Page slice structure

src/pages/product-detail/
  ui/
    ProductDetailPage.vue      ← Main page component
    ProductGallery.vue
    ProductSpecs.vue
  model/
    useProductDetail.ts        ← Page-specific composable
  api/
    queries.ts                 ← Page-specific data fetching
  index.ts                     ← Public API: exports ProductDetailPage
// src/pages/product-detail/index.ts
export { default as ProductDetailPage } from './ui/ProductDetailPage.vue'

Imports and aliases

Primary pattern: ~~/src/ prefix

Use Nuxt's ~~/ alias (which resolves to the project root) to import from FSD layers:

// CORRECT — primary import pattern
import { useAuth } from '~~/src/features/auth'
import { type User } from '~~/src/entities/user'
import { UiButton } from '~~/src/shared/ui'
import { ProductDetailPage } from '~~/src/pages/product-detail'

Alternative: custom path aliases

You can optionally configure shorter aliases in nuxt.config.ts:

// nuxt.config.ts
export default defineNuxtConfig({
  alias: {
    '@shared': fileURLToPath(new URL('./src/shared', import.meta.url)),
    '@entities': fileURLToPath(new URL('./src/entities', import.meta.url)),
    '@features': fileURLToPath(new URL('./src/features', import.meta.url)),
    '@widgets': fileURLToPath(new URL('./src/widgets', import.meta.url)),
  },
})

Then use: import { UiButton } from '@shared/ui'. Both patterns are valid — ~~/src/ requires no config, aliases are shorter.

Important: Do NOT rely on Nuxt auto-imports for cross-layer dependencies. Always use explicit imports through the slice's public API (index.ts). This keeps dependencies visible and enforceable.

// CORRECT — explicit import via public API
import { useAuth } from '~~/src/features/auth'

// WRONG — deep import bypassing public API
import { useAuth } from '~~/src/features/auth/model/useAuth'

Nuxt auto-imports are acceptable only for:

  • Vue/Nuxt built-ins (ref, computed, useFetch, useRoute, navigateTo, etc.)
  • Global app-layer composables in app/composables/

TypeScript configuration

Since src/ lives outside the Nuxt app/ directory, you must explicitly include it in the TypeScript config:

// nuxt.config.ts
export default defineNuxtConfig({
  typescript: {
    tsConfig: {
      include: ['../src/**/*'],
    },
    sharedTsConfig: {
      include: ['../src/**/*'],
    },
    nodeTsConfig: {
      include: ['../src/**/*'],
    },
  },
})

This ensures all three Nuxt-generated tsconfigs (client, shared, server) include type-checking and auto-completion for FSD code in src/.


Slices and segments

A slice is a subfolder inside a layer, named after a business domain concept:

  • src/features/auth, src/entities/user, src/widgets/product-card

A segment groups code by technical role inside a slice:

src/features/auth/
  ui/          ← Vue components
  model/       ← Composables, stores (Pinia), state, types
  api/         ← Data fetching (useFetch, $fetch, query wrappers)
  lib/         ← Helpers specific to this slice
  config/      ← Constants, feature flags
  index.ts     ← Public API (REQUIRED)

Naming conventions

  • kebab-case everywhere: user-profile, add-to-cart, product-card
  • Domain-first names, NOT technical: auth not use-auth-hook, product not product-helpers
  • Vue components: PascalCase filenames matching component name: LoginForm.vue, ProductCard.vue
  • Composables: camelCase with use prefix: useAuth.ts, useProductSearch.ts

Public API

Every slice must expose an index.ts. External code imports only from index.ts.

// src/features/auth/index.ts
export { default as LoginForm } from './ui/LoginForm.vue'
export { useAuth } from './model/useAuth'
export { useProvideAuth } from './model/useAuth'
export type { AuthUser, AuthCredentials } from './model/types'

Deep imports are forbidden between slices:

// WRONG
import { useAuth } from '~~/src/features/auth/model/useAuth'

// CORRECT
import { useAuth } from '~~/src/features/auth'

Within-slice relative imports are fine:

// Inside src/features/auth/ui/LoginForm.vue
import { useAuth } from '../model/useAuth'

Pages-first workflow

Start everything in src/pages/. Extract only when reuse is proven.

New functionality needed
        │
        v
  Build inside src/pages/<slice>/
        │
        v
  Used in a 2nd place?
     NO --> Keep in src/pages/
     YES
        │
        v
  Duplicate it (2 copies is acceptable)
        │
        v
  Used in a 3rd place?
     NO --> Keep duplicated
     YES
        │
        v
  Extract. Ask: "Is this logic always tied to THIS specific UI?"
     YES --> Widget (logic + UI coupled)
      NO --> Feature (logic reusable, UI replaceable)
             or Entity (pure domain data, no user action)

Rule: Extract when you have evidence (3+ usages), not a prediction. Premature extraction creates unnecessary abstraction.


Widget vs Feature decision

The critical question: "Can the logic be used WITHOUT this specific UI?"

Widget — logic and UI are inseparable

  • Always rendered as a unit
  • Contains its own data fetching and state
  • Reused across 2+ pages but always as a whole block
src/widgets/job-list/
  ui/
    JobList.vue          ← Fetches data, renders list, handles pagination
    JobListItem.vue      ← Presentational sub-component
  model/
    useJobList.ts        ← Encapsulated composable, not exported alone
  index.ts               ← Exports only <JobList />

Feature — logic is standalone

  • Composable works with any UI
  • Default UI is provided but replaceable
  • Logic can be used headlessly
src/features/search/
  model/
    useSearch.ts         ← Works standalone, any UI can consume it
  ui/
    SearchBar.vue        ← Default UI, receives state via props/inject
  index.ts               ← Exports both useSearch and SearchBar

Quick decision table

Module Widget or Feature? Why
Search with debounced results Feature useSearch powers different UIs
Infinite scroll product list Widget Fetch + render always together
Auth login form Feature useAuth reusable in modal, page, drawer
Navigation sidebar Widget Nav items + layout always coupled
Like/bookmark button Feature useLike works standalone
Dashboard stats panel Widget Chart + data always together

State management placement

All state lives in the model/ segment of the relevant slice:

Pattern When to use Placement
Simple composable (useState) Shared state within a feature/entity, SSR-safe src/features/auth/model/useAuth.ts
Provider/Inject (createInjectionState) State scoped to a component subtree src/features/cart/model/useCart.ts
Pinia store Global state, devtools, persistence, complex actions src/entities/user/model/store.ts

Provider/Inject pattern

Use createInjectionState from @vueuse/core for scoped state. Always export a throwing variant so consumers get a clear error if the provider is missing:

// src/features/auth/model/useAuth.ts
import { createInjectionState } from '@vueuse/core'

const [useProvideAuth, useAuthRaw] = createInjectionState(() => {
  const user = ref<User | null>(null)
  return { user }
})

export { useProvideAuth }

export function useAuth() {
  const state = useAuthRaw()
  if (!state) throw new Error('useAuth must be used within AuthProvider')
  return state
}

Data fetching placement

What Where Example path
Base queries (read) Entity api/ segment src/entities/product/api/queries.ts
Mutations (write) Feature api/ segment src/features/add-to-cart/api/mutations.ts
Page-specific fetching Page slice api/ or inline src/pages/product-detail/api/queries.ts

Import rules (strict)

Layer imports — only downward (within src/)

pages     can import from → widgets, features, entities, shared
widgets   can import from → features, entities, shared
features  can import from → entities, shared
entities  can import from → shared
shared    can import from → (nothing — only external packages)

app/ (Nuxt shell) can import from any FSD layer in src/.

Same-layer isolation

Slices within the same layer cannot import from each other:

// WRONG — features/auth importing from features/profile
import { useProfile } from '~~/src/features/profile'

// CORRECT — extract shared concept to entities or shared
import { type User } from '~~/src/entities/user'

Cross-entity references (@x notation)

When entities have legitimate business relationships, use explicit @x cross-references:

// src/entities/order/ui/OrderCard.vue
import { UserAvatar } from '~~/src/entities/user/@x/order'

The @x/<consumer> folder is a controlled cross-import API. Use sparingly.


Cross-slice communication patterns

When slices on the same layer need to coordinate:

  1. Extract to a lower layer — if two features share a concept, move it to entities or shared
  2. Event-based communication — use a shared event bus (mitt) or Pinia store for loose coupling
  3. Composition at a higher layer — let a page or widget compose multiple features together

Nuxt app/ directory (not FSD)

Everything in app/ follows Nuxt conventions, not FSD. These files consume FSD code from src/ but are not themselves organized into FSD layers:

Nuxt directory Role Relation to FSD
app/pages/ Thin routing shells Imports page components from src/pages/
app/layouts/ Nuxt layouts May import widgets from src/widgets/
app/middleware/ Route middleware May import composables from src/features/ or src/entities/
app/plugins/ Global setup May import from any src/ layer
app/composables/ Global composables Only truly app-wide concerns, not FSD-sliced code
server/ Server routes Entirely outside FSD, follows Nuxt server conventions

Shared layer structure

The shared layer has no slices — only segments:

src/shared/
  ui/               ← Generic UI components: buttons, modals, inputs, cards
  lib/              ← Utility functions: formatDate, cn(), debounce
  api/              ← API client setup, interceptors, base fetch wrapper
  config/           ← App-wide constants, env helpers, route names
  types/            ← Shared TypeScript utility types

Each segment can have its own index.ts for cleaner imports.


Scaffolding a new slice

src/features/bookmark/
  ui/
    BookmarkButton.vue
  model/
    useBookmark.ts
    types.ts
  api/
    mutations.ts
  index.ts              ← Public API (REQUIRED)

Only create segments you actually need. An entity with only types needs only model/ and index.ts.


Common mistakes

1. Putting FSD layers inside app/

Wrong: app/features/, app/entities/, app/widgets/, app/shared/. Right: FSD layers live in src/. Only app/pages/ (thin shells), app/layouts/, app/plugins/, app/middleware/, app/composables/ go in app/.

2. Fat page routes

Wrong: Putting full page implementations in app/pages/products/[id].vue (200+ lines). Right: app/pages/ files are thin routing shells (5–25 lines) that import from src/pages/.

3. Premature extraction

Wrong: Creating src/features/fancy-button because "it might be reused." Right: Keep in src/pages/ until 3 actual usages prove the need.

4. Wrong layer

Wrong: Putting useProductSearch (standalone logic) in a widget. Right: Ask "can this logic work without THIS specific UI?" — yes means Feature.

5. Missing public API

Wrong: import { x } from '~~/src/features/auth/model/internal' Right: All exports go through index.ts.

6. Upward imports

Wrong: src/entities/user importing from src/features/auth. Right: Dependency flows only downward — entities never reference features.

7. Same-layer cross-imports

Wrong: src/features/profile importing from src/features/auth. Right: Extract shared concept to entities/ or use events.

8. Business logic in shared

Wrong: src/shared/lib/useAuth.ts, src/shared/lib/useProductSearch.ts. Right: Shared is for generic utilities only — zero business logic.

9. Relying on Nuxt auto-imports for FSD layers

Wrong: Expecting useAuth() to auto-resolve from src/features/auth/model/. Right: Use explicit imports via ~~/src/features/auth public API.

10. Applying FSD outside src/

Wrong: Organizing app/composables/, app/middleware/, or server/ into FSD layers. Right: FSD lives exclusively in src/. Everything else (app/, server/) follows Nuxt conventions.


Checklist for new code

Before writing or reviewing code, verify:

  • [ ] Which layer does this belong to? (Use the decision tree)
  • [ ] Does the slice have an index.ts public API?
  • [ ] Are all cross-slice imports going through index.ts?
  • [ ] Am I importing only from layers below?
  • [ ] Are imports using ~~/src/ prefix (or configured aliases)?
  • [ ] Is app/pages/*.vue a thin routing shell (<25 lines)?
  • [ ] Widget or Feature? (Asked: "can logic exist without this UI?")
  • [ ] Is extraction proven (3+ usages) or premature?
  • [ ] Is all FSD-structured code in src/, not in app/ or server/?
  • [ ] Does app/ only contain Nuxt runtime concerns (thin pages, layouts, plugins, middleware)?

Migration strategy

For existing Nuxt projects adopting FSD with src/ root:

Code Approach
New modules Always follow FSD in src/
Existing app/components/ Migrate to src/shared/ui/ or appropriate slice ui/ segment
Existing app/composables/ Classify into feature/entity model/ in src/ or keep in app/composables/ if truly global
Existing app/utils/ Move to src/shared/lib/
Existing app/stores/ Move to entity/feature model/ segments in src/
Existing fat pages Split into thin shell in app/pages/ + page slice in src/pages/

Steps:

  1. Create src/ directory with FSD layer subdirectories
  2. Add typescript includes (tsConfig, sharedTsConfig, nodeTsConfig) to nuxt.config.ts
  3. Start all new code in FSD structure under src/
  4. Migrate existing code slice-by-slice when touched
  5. Convert fat pages to thin routing shells + src/pages/ slices
  6. Update imports to use ~~/src/ and public APIs
  7. Remove old directories once empty