การใช้ร่วมกับ Next.js
FSD สามารถใช้ร่วมกับ Next.js ได้ทั้งเวอร์ชัน App Router และ Pages Router หากคุณแก้ปัญหาความขัดแย้งหลักได้ — นั่นคือเรื่องโฟลเดอร์ app และ pages
App Router
ความขัดแย้งระหว่าง FSD และ Next.js ใน app layer
Next.js แนะนำให้ใช้โฟลเดอร์ app เพื่อกำหนด Application routes มันคาดหวังให้ไฟล์ในโฟลเดอร์ app ตรงกับ Pathnames กลไกการ Routing นี้ ไม่ตรงกัน กับแนวคิดของ FSD เนื่องจากมันทำให้ไม่สามารถรักษาโครงสร้าง Slice แบบแบนราบ (Flat slice structure) ได้
ทางแก้คือย้ายโฟลเดอร์ app ของ Next.js ไปไว้ที่ Project root และ Import FSD pages จาก src (ซึ่งเป็นที่อยู่ของ FSD layers) เข้าไปในโฟลเดอร์ app ของ Next.js
คุณจะต้องเพิ่มโฟลเดอร์ pages ไว้ที่ Project root ด้วย ไม่อย่างนั้น Next.js จะพยายามใช้ src/pages เป็น Pages Router แม้ว่าคุณจะใช้ App Router ก็ตาม ซึ่งจะทำให้ Build พัง และเป็นความคิดที่ดีที่จะใส่ไฟล์ README.md ไว้ใน Root pages folder นี้เพื่ออธิบายว่าทำไมมันถึงจำเป็นต้องมี แม้ว่าข้างในจะว่างเปล่าก็ตาม
├── app # App folder (Next.js)
│ ├── api
│ │ └── get-example
│ │ └── route.ts
│ └── example
│ └── page.tsx
├── pages # Empty pages folder (Next.js)
│ └── README.md
└── src
├── app
│ └── api-routes # API routes
├── pages
│ └── example
│ ├── index.ts
│ └── ui
│ └── example.tsx
├── widgets
├── features
├── entities
└── shared
ตัวอย่างการ Re-export page จาก src/pages ใน app ของ Next.js:
export { ExamplePage as default, metadata } from '@/pages/example';
Middleware
ถ้าคุณใช้ Middleware ในโปรเจกต์ของคุณ มันต้องวางอยู่ที่ Project root ข้างๆ โฟลเดอร์ app และ pages ของ Next.js
Instrumentation
ไฟล์ instrumentation.js ช่วยให้คุณ Monitor ประสิทธิภาพและพฤติกรรมของ Application ถ้าคุณใช้มัน มันต้องวางอยู่ที่ Project root เหมือนกับ middleware.js
Pages Router
ความขัดแย้งระหว่าง FSD และ Next.js ใน pages layer
Routes ควรวางอยู่ในโฟลเดอร์ pages ใน Root ของโปรเจกต์ คล้ายกับโฟลเดอร์ app สำหรับ App Router โครงสร้างภายใน src ที่เป็นที่ตั้งของ Layer folders จะยังคงเหมือนเดิม
├── pages # Pages folder (Next.js)
│ ├── _app.tsx
│ ├── api
│ │ └── example.ts # API route re-export
│ └── example
│ └── index.tsx
└── src
├── app
│ ├── custom-app
│ │ └── custom-app.tsx # Custom App component
│ └── api-routes
│ └── get-example-data.ts # API route
├── pages
│ └── example
│ ├── index.ts
│ └── ui
│ └── example.tsx
├── widgets
├── features
├── entities
└── shared
ตัวอย่างการ Re-export page จาก src/pages ใน pages ของ Next.js:
export { Example as default } from '@/pages/example';
Custom _app component
คุณสามารถวาง Custom App component ใน src/app/_app หรือ src/app/custom-app:
import type { AppProps } from 'next/app';
export const MyApp = ({ Component, pageProps }: AppProps) => {
return (
<>
<p>My Custom App component</p>
<Component { ...pageProps } />
</>
);
};
export { App as default } from '@/app/custom-app';
Route Handlers (API routes)
ใช้ api-routes segment ใน app layer เพื่อทำงานกับ Route Handlers
ระวังเมื่อเขียน Backend code ในโครงสร้าง FSD — FSD มีเป้าหมายหลักสำหรับ Frontend ซึ่งหมายความว่าเป็นสิ่งที่คนจะคาดหวังว่าจะเจอ ถ้าคุณมี Endpoints เยอะ พิจารณาแยกพวกมันออกเป็น Package ต่างหากใน Monorepo
- App Router
- Pages Router
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',
});
}
};
export { getExampleData as GET } from '@/app/api-routes';
import type { NextApiRequest, NextApiResponse } from 'next';
const config = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
maxDuration: 5,
};
const handler = (req: NextApiRequest, res: NextApiResponse<ResponseData>) => {
res.status(200).json({ message: 'Hello from FSD' });
};
export const getExampleData = { config, handler } as const;
export { getExampleData } from './get-example-data';
import { getExampleData } from '@/app/api-routes';
export const config = getExampleData.config;
export default getExampleData.handler;
คำแนะนำเพิ่มเติม
- ใช้
dbsegment ในsharedlayer เพื่ออธิบาย Database queries และการใช้งานต่อใน Layers ที่สูงกว่า - Logic การทำ Caching และ Revalidating queries ควรเก็บไว้ที่เดียวกับ Queries เอง