Files
lcbp3/specs/05-decisions/ADR-011-nextjs-app-router.md
admin 83704377f4
Some checks are pending
Spec Validation / validate-markdown (push) Waiting to run
Spec Validation / validate-diagrams (push) Waiting to run
Spec Validation / check-todos (push) Waiting to run
251218:1701 On going update to 1.7.0: Documnet Number rebuild
2025-12-18 17:01:42 +07:00

10 KiB

ADR-011: Next.js App Router & Routing Strategy

Status: Accepted Date: 2025-12-01 Decision Makers: Frontend Team, System Architect Related Documents: Frontend Guidelines, ADR-005: Technology Stack


Context and Problem Statement

Next.js มี 2 รูปแบบ Router หลัก: Pages Router (เก่า) และ App Router (ใหม่ใน Next.js 13+) ต้องเลือกว่าจะใช้แบบไหนสำหรับ LCBP3-DMS

ปัญหาที่ต้องแก้:

  1. Routing Architecture: ใช้ Pages Router หรือ App Router
  2. Server vs Client Components: จัดการ Data Fetching อย่างไร
  3. Layout System: จัดการ Shared Layouts อย่างไร
  4. Performance: ทำอย่างไรให้ Initial Load เร็ว
  5. SEO: ต้องการ SEO หรือไม่ (Dashboard ไม่ต้องการ)

Decision Drivers

  • 🚀 Performance: Initial load time และ Navigation speed
  • 🎯 Developer Experience: ง่ายต่อการพัฒนาและบำรุงรักษา
  • 📦 Code Organization: โครงสร้างโค้ดชัดเจน
  • 🔄 Future-Proof: พร้อมสำหรับ Next.js รุ่นถัดไป
  • 🎨 Layout Flexibility: จัดการ Nested Layouts ได้ง่าย

Considered Options

Option 1: Pages Router (Traditional)

โครงสร้าง:

pages/
  ├── _app.tsx
  ├── _document.tsx
  ├── index.tsx
  ├── correspondences/
  │   ├── index.tsx
  │   └── [id].tsx
  └── api/
      └── ...

Pros:

  • Mature และ Stable
  • Documentation ครบถ้วน
  • Community ใหญ่
  • ทีมคุ้นเคยแล้ว

Cons:

  • ไม่รองรับ Server Components
  • Layout System ซับซ้อน (ต้องใช้ HOC)
  • Data Fetching ไม่ทันสมัย
  • Not recommended for new projects

โครงสร้าง:

app/
  ├── layout.tsx           # Root layout
  ├── page.tsx             # Home page
  ├── correspondences/
  │   ├── layout.tsx       # Nested layout
  │   ├── page.tsx         # List page
  │   └── [id]/
  │       └── page.tsx     # Detail page
  └── (auth)/
      ├── layout.tsx
      └── login/
          └── page.tsx

Pros:

  • Server Components (Better performance)
  • Built-in Layout System
  • Streaming & Suspense support
  • Better Data Fetching patterns
  • Recommended by Next.js team

Cons:

  • Newer (less community resources)
  • Learning curve สำหรับทีม
  • Some libraries ยังไม่รองรับ

Option 3: Hybrid Approach

ใช้ App Router + Pages Router พร้อมกัน

Pros:

  • Gradual migration

Cons:

  • เพิ่มความซับซ้อน
  • Confusing สำหรับทีม

Decision Outcome

Chosen Option: Option 2 - App Router

Rationale

  1. Future-Proof: Next.js แนะนำให้ใช้ App Router สำหรับโปรเจกต์ใหม่
  2. Performance: Server Components ช่วยลด JavaScript bundle size
  3. Better DX: Layout System สะดวกกว่า
  4. Server Actions: รองรับ Form submissions โดยไม่ต้องสร้าง API routes
  5. Learning Investment: Team จะได้ Skill ที่ทันสมัย

Implementation Details

1. Folder Structure

app/
  ├── (public)/              # Public routes (no auth)
  │   ├── layout.tsx
  │   └── login/
  │       └── page.tsx
  │
  ├── (dashboard)/           # Protected routes
  │   ├── layout.tsx         # Dashboard layout with sidebar
  │   ├── page.tsx           # Dashboard home
  │   │
  │   ├── correspondences/
  │   │   ├── layout.tsx
  │   │   ├── page.tsx       # List
  │   │   ├── new/
  │   │   │   └── page.tsx   # Create
  │   │   └── [id]/
  │   │       ├── page.tsx   # Detail
  │   │       └── edit/
  │   │           └── page.tsx
  │   │
  │   ├── rfas/
  │   ├── drawings/
  │   └── settings/
  │
  ├── api/                   # API route handlers (minimal)
  │   └── auth/
  │       └── [...nextauth]/
  │           └── route.ts
  │
  ├── layout.tsx             # Root layout
  └── page.tsx               # Root redirect

2. Root Layout

// File: app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'LCBP3-DMS',
  description: 'Document Management System for Laem Chabang Port Phase 3',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="th">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

3. Dashboard Layout (with Sidebar)

// File: app/(dashboard)/layout.tsx
import { Sidebar } from '@/components/layout/sidebar';
import { Header } from '@/components/layout/header';
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth';

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  // Server-side auth check
  const session = await getServerSession();

  if (!session) {
    redirect('/login');
  }

  return (
    <div className="flex h-screen">
      <Sidebar />
      <div className="flex flex-1 flex-col overflow-hidden">
        <Header />
        <main className="flex-1 overflow-auto p-6">{children}</main>
      </div>
    </div>
  );
}

4. Server Component (Data Fetching)

// File: app/(dashboard)/correspondences/page.tsx
import { CorrespondenceList } from '@/components/correspondences/list';
import { getCorrespondences } from '@/lib/api/correspondences';

export default async function CorrespondencesPage({
  searchParams,
}: {
  searchParams: { page?: string; status?: string };
}) {
  // Fetch data on server
  const correspondences = await getCorrespondences({
    page: parseInt(searchParams.page || '1'),
    status: searchParams.status,
  });

  return (
    <div>
      <h1 className="text-2xl font-bold mb-6">Correspondences</h1>
      <CorrespondenceList data={correspondences} />
    </div>
  );
}

5. Client Component (Interactive)

// File: components/correspondences/list.tsx
'use client'; // Client Component

import { useState } from 'react';
import { Correspondence } from '@/types';

export function CorrespondenceList({ data }: { data: Correspondence[] }) {
  const [filter, setFilter] = useState('');

  const filtered = data.filter((item) =>
    item.subject.toLowerCase().includes(filter.toLowerCase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="Filter..."
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        className="border p-2 mb-4"
      />
      <div>
        {filtered.map((item) => (
          <div key={item.id}>{item.subject}</div>
        ))}
      </div>
    </div>
  );
}

6. Loading States

// File: app/(dashboard)/correspondences/loading.tsx
export default function Loading() {
  return (
    <div className="space-y-4">
      <div className="h-8 bg-gray-200 rounded animate-pulse" />
      <div className="h-64 bg-gray-200 rounded animate-pulse" />
    </div>
  );
}

7. Error Handling

// File: app/(dashboard)/correspondences/error.tsx
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div className="p-4">
      <h2 className="text-xl font-bold text-red-600">Something went wrong!</h2>
      <p className="text-gray-600">{error.message}</p>
      <button
        onClick={reset}
        className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
      >
        Try again
      </button>
    </div>
  );
}

Routing Patterns

Route Groups (Organization)

(public)/     # Public pages
(dashboard)/  # Protected dashboard
(auth)/       # Auth-related pages

Dynamic Routes

[id]/         # Dynamic segment (e.g., /correspondences/123)
[...slug]/    # Catch-all (e.g., /docs/a/b/c)

Parallel Routes & Intercepting Routes

@modal/       # Parallel route for modals
(.)/          # Intercept same level

Consequences

Positive Consequences

  1. Better Performance: Server Components ลด Client JavaScript
  2. SEO-Friendly: Server-side rendering out of the box
  3. Simpler Layouts: Nested layouts ทำได้ง่าย
  4. Streaming: Progressive rendering with Suspense
  5. Future-Proof: ทิศทางของ Next.js และ React

Negative Consequences

  1. Learning Curve: ทีมต้องเรียนรู้ Server Components
  2. Limited Libraries: บาง Libraries ยังไม่รองรับ Server Components
  3. Debugging: ยากกว่า Pages Router เล็กน้อย

Mitigation Strategies

  • Training: จัด Workshop เรื่อง App Router และ Server Components
  • Documentation: เขียน Internal docs สำหรับ Patterns ที่ใช้
  • Code Review: Review code ให้ใช้ Server/Client Components ถูกต้อง
  • Gradual Adoption: เริ่มจาก Simple pages ก่อน


References


Last Updated: 2025-12-01 Next Review: 2026-06-01