# 路由架構指南 ## 🗺️ 路由結構總覽 DramaLing 使用 Next.js 14 App Router,採用檔案系統路由。 ``` app/ ├── (auth)/ # 認證相關頁面群組 │ ├── login/ │ ├── signup/ │ └── forgot-password/ ├── (dashboard)/ # 需要登入的頁面群組 │ ├── layout.tsx # 共用 Dashboard Layout │ ├── page.tsx # Dashboard 首頁 │ ├── flashcards/ │ ├── decks/ │ ├── progress/ │ └── settings/ ├── api/ # API 路由 ├── layout.tsx # 根 Layout ├── page.tsx # 首頁 └── not-found.tsx # 404 頁面 ``` ## 📁 詳細路由規劃 ### 公開路由(無需登入) | 路徑 | 頁面 | 說明 | |------|------|------| | `/` | 首頁 | Landing page,產品介紹 | | `/features` | 功能介紹 | 詳細功能說明 | | `/pricing` | 價格方案 | 訂閱方案(未來) | | `/about` | 關於我們 | 團隊介紹 | | `/privacy` | 隱私政策 | 法律文件 | | `/terms` | 使用條款 | 法律文件 | ### 認證路由 | 路徑 | 頁面 | 說明 | |------|------|------| | `/login` | 登入 | Email/密碼或 Google 登入 | | `/signup` | 註冊 | 新用戶註冊 | | `/forgot-password` | 忘記密碼 | 密碼重設請求 | | `/reset-password` | 重設密碼 | 實際重設密碼 | | `/verify-email` | 驗證信箱 | Email 驗證頁面 | ### 受保護路由(需登入) | 路徑 | 頁面 | 說明 | |------|------|------| | `/dashboard` | 儀表板 | 用戶主頁,學習統計 | | `/flashcards` | 詞卡列表 | 所有詞卡總覽 | | `/flashcards/new` | 新增詞卡 | AI 生成詞卡 | | `/flashcards/[id]` | 詞卡詳情 | 單一詞卡檢視/編輯 | | `/decks` | 卡組列表 | 詞卡分類管理 | | `/decks/[id]` | 卡組詳情 | 特定卡組的詞卡 | | `/learn/[deckId]` | 學習模式 | 間隔重複學習 | | `/progress` | 學習進度 | 統計與成就 | | `/settings` | 設定 | 個人資料與偏好 | ## 🔧 路由實作 ### 1. 根 Layout ```typescript // app/layout.tsx import { Inter } from 'next/font/google' import { Providers } from './providers' import { Toaster } from '@/components/ui/toaster' import './globals.css' const inter = Inter({ subsets: ['latin'] }) export const metadata = { title: 'DramaLing - Learn English with Drama', description: 'Master English vocabulary through your favorite TV shows', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( {children} ) } ``` ### 2. 路由群組 Layout ```typescript // app/(dashboard)/layout.tsx import { redirect } from 'next/navigation' import { getServerSession } from '@/lib/auth' import { Sidebar } from '@/components/layout/Sidebar' import { Header } from '@/components/layout/Header' export default async function DashboardLayout({ children, }: { children: React.ReactNode }) { const session = await getServerSession() if (!session) { redirect('/login') } return (
{children}
) } ``` ### 3. 動態路由 ```typescript // app/(dashboard)/flashcards/[id]/page.tsx import { notFound } from 'next/navigation' import { getFlashcard } from '@/lib/api/flashcards' interface PageProps { params: { id: string } } export default async function FlashcardPage({ params }: PageProps) { const flashcard = await getFlashcard(params.id) if (!flashcard) { notFound() } return (

{flashcard.word}

{flashcard.translation}

{/* 詞卡詳情 */}
) } // 生成靜態參數(可選) export async function generateStaticParams() { const flashcards = await getPopularFlashcards() return flashcards.map((card) => ({ id: card.id, })) } ``` ### 4. 平行路由(Modal) ```typescript // app/(dashboard)/@modal/(.)flashcards/[id]/page.tsx import { Modal } from '@/components/ui/modal' import FlashcardDetail from '@/components/FlashcardDetail' export default function FlashcardModal({ params }: { params: { id: string } }) { return ( ) } // app/(dashboard)/layout.tsx export default function Layout({ children, modal, }: { children: React.ReactNode modal: React.ReactNode }) { return ( <> {children} {modal} ) } ``` ## 🛡️ 路由保護 ### 1. Middleware 認證檢查 ```typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' import { getToken } from 'next-auth/jwt' // 需要認證的路由 const protectedRoutes = [ '/dashboard', '/flashcards', '/decks', '/learn', '/progress', '/settings', ] // 已登入用戶不應訪問的路由 const authRoutes = ['/login', '/signup', '/forgot-password'] export async function middleware(request: NextRequest) { const token = await getToken({ req: request }) const { pathname } = request.nextUrl // 檢查受保護路由 const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route) ) if (isProtectedRoute && !token) { const url = new URL('/login', request.url) url.searchParams.set('callbackUrl', pathname) return NextResponse.redirect(url) } // 已登入用戶重定向 const isAuthRoute = authRoutes.some(route => pathname.startsWith(route) ) if (isAuthRoute && token) { return NextResponse.redirect(new URL('/dashboard', request.url)) } return NextResponse.next() } export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], } ``` ### 2. 客戶端路由守衛 ```typescript // components/auth/AuthGuard.tsx 'use client' import { useEffect } from 'react' import { useRouter } from 'next/navigation' import { useStore } from '@/store' interface AuthGuardProps { children: React.ReactNode fallback?: React.ReactNode } export function AuthGuard({ children, fallback }: AuthGuardProps) { const router = useRouter() const { isAuthenticated, isLoading, checkAuth } = useStore() useEffect(() => { checkAuth() }, []) useEffect(() => { if (!isLoading && !isAuthenticated) { router.push('/login') } }, [isAuthenticated, isLoading, router]) if (isLoading) { return fallback ||
Loading...
} if (!isAuthenticated) { return null } return <>{children} } ``` ## 🔀 路由導航 ### 1. 程式化導航 ```typescript // components/FlashcardList.tsx 'use client' import { useRouter } from 'next/navigation' export function FlashcardList() { const router = useRouter() const handleCardClick = (id: string) => { router.push(`/flashcards/${id}`) } const handleCreateNew = () => { router.push('/flashcards/new') } return ( // ... ) } ``` ### 2. Link 組件 ```typescript // components/Navigation.tsx import Link from 'next/link' export function Navigation() { return ( ) } ``` ### 3. 動態麵包屑 ```typescript // components/Breadcrumbs.tsx 'use client' import { usePathname } from 'next/navigation' import Link from 'next/link' export function Breadcrumbs() { const pathname = usePathname() const segments = pathname.split('/').filter(Boolean) return ( ) } ``` ## 📱 路由載入狀態 ### 1. Loading UI ```typescript // app/(dashboard)/flashcards/loading.tsx export default function Loading() { return (
{[...Array(6)].map((_, i) => (
))}
) } ``` ### 2. Error Boundary ```typescript // app/(dashboard)/flashcards/error.tsx 'use client' export default function Error({ error, reset, }: { error: Error & { digest?: string } reset: () => void }) { return (

Something went wrong!

{error.message}

) } ``` ## 🎯 路由最佳實踐 ### 1. SEO 優化 ```typescript // app/(dashboard)/flashcards/page.tsx import { Metadata } from 'next' export const metadata: Metadata = { title: 'Flashcards | DramaLing', description: 'Manage your vocabulary flashcards', openGraph: { title: 'Flashcards | DramaLing', description: 'Learn English vocabulary with AI-powered flashcards', }, } // 動態 metadata export async function generateMetadata({ params }: PageProps): Promise { const flashcard = await getFlashcard(params.id) return { title: `${flashcard.word} | DramaLing`, description: flashcard.translation, } } ``` ### 2. 路由預載入策略 ```typescript // components/FlashcardGrid.tsx import Link from 'next/link' export function FlashcardGrid({ flashcards }: { flashcards: Flashcard[] }) { return (
{flashcards.map((card, index) => ( ))}
) } ``` ### 3. 查詢參數處理 ```typescript // app/(dashboard)/flashcards/page.tsx import { Suspense } from 'react' interface PageProps { searchParams: { page?: string sort?: 'recent' | 'alphabetical' | 'difficulty' filter?: string } } export default function FlashcardsPage({ searchParams }: PageProps) { const page = Number(searchParams.page) || 1 const sort = searchParams.sort || 'recent' const filter = searchParams.filter return ( }> ) } ``` ## 🚀 路由性能優化 ### 1. 部分預渲染 ```typescript // app/(dashboard)/flashcards/page.tsx export const dynamic = 'force-dynamic' // 動態渲染 export const revalidate = 60 // ISR:60 秒重新驗證 ``` ### 2. 路由分割 ```typescript // 使用動態導入減少初始載入 const FlashcardEditor = dynamic( () => import('@/components/FlashcardEditor'), { loading: () =>

Loading editor...

, ssr: false, // 客戶端渲染 } ) ``` ### 3. 路由快取策略 ```typescript // app/api/flashcards/route.ts export async function GET(request: Request) { // 設定快取標頭 return NextResponse.json(data, { headers: { 'Cache-Control': 'public, s-maxage=10, stale-while-revalidate=59', }, }) } ```