Derived Modular Arch
指南

Next.js

App Router 上的 DMA:Server Components、Client 边界、providers。

本示例展示 DMA 如何适配 Next.js App Router 的 RSC 边界。v1 不是完整可运行 app——源码足以运行 dma check 和 ESLint。

源码:examples/next-app

目录树

src/
├── app/
│   ├── layout.tsx       # Server — shell + <Providers>
│   ├── page.tsx         # Server — thin route
│   ├── providers.tsx    # Client — SessionProvider
│   └── globals.scss
├── features/
│   ├── catalog/         # catalog.store + public/catalog-page
│   ├── checkout/
│   ├── search/
│   └── profile.tsx      # stage 0
├── services/
│   ├── cart/public/
│   └── session/public/
└── shared/
    ├── domain/
    ├── lib/
    └── ui/

方向:app/pages/routes → features → services → shared。Feature 之间互不导入。

Server 与 Client

Next.js 默认为 Server Components。DMA 在组合根feature 公共入口之间划分边界:

文件边界原因
layout.tsxServerHTML 外壳,无 hooks
page.tsxServer只挂载 */public/*
providers.tsxClient("use client"Context 需要 client 边界
catalog-page.tsxClientonClick、store
button.tsxServer-safe"use client"——经 feature 拉入 client bundle
app/layout.tsx          (Server)
  └── app/providers.tsx (Client)
        └── app/page.tsx        (Server)
              ├── features/catalog/public/catalog-page.tsx   (Client)
              ├── features/search/public/search-page.tsx     (Client)
              └── features/profile.tsx                       (Client)

模式: page.tsx 保持精简的 server 组合根。交互逻辑在 feature 的 public/ 入口。不要从 page.tsx 导入 *.store.ts

精简的 page.tsx

import { CatalogPage } from "@/features/catalog/public/catalog-page";
import { CheckoutPage } from "@/features/checkout/public/checkout-page";
import { Profile } from "@/features/profile";
import { SearchPage } from "@/features/search/public/search-page";
// ❌ never in app/
import { catalogStore } from "@/features/catalog/catalog.store";

app 中的 Providers

横切上下文(session)——在 app/providers.tsx

"use client";
import { SessionProvider } from "@/services/session/public/session-provider";

export function Providers({ children }) {
  return <SessionProvider>{children}</SessionProvider>;
}

profile.tsx 读取 useSession()——provider 在 app 中挂载,而非 feature 中。

Services

模块消费者角色
cartcatalog、checkoutaddToCartcartTotal
sessionprofile(经 provider)SessionProvider

services/ 出现是因为两个模块都导入 cart——从 colocation 提升(promotion)而来。

样式

类型位置示例
全局app/globals.scssreset、外壳
SCSS module紧邻 public/ 页面catalog-page.module.scss
CSS module紧邻 public/ 页面checkout-page.module.css

全局样式——只从 layout.tsx 导入。Feature 不导入全局样式。

工具

bun run dma-check   # dma check .
bun run lint        # eslint + compositionRoots: ["app", "pages", "routes"]
bun run test

ESLint 捕获文件级错误(从 page.tsx 导入 store)。完整导入图——仅 dma check

下一步

On this page