昨天我們完成了 Tailwind CSS 與 shadcn/ui 的 Design System(設計系統)配置。今天,我們要正式把應用程式的「骨架」搭起來!
一個現代化 Web App 的外框(Layout)不能只顧著好看,還必須滿足以下關鍵條件:
今天我們就來示範如何透過提示詞引導 AI,快速刻出一個兼具美觀與功能的響應式 App Layout!
要讓 AI 寫出高質量的 App Layout,我們不能只給「幫我做一個 Sidebar」這種空泛的指令,而是要明確定義 Next.js App Router 的結構與 shadcn/ui 元件。
你現在是一位資深前端架構師。請幫我為 Next.js (App Router) 專案建立 App 核心 Layout 結構。
需求如下:
1. 建立 `Sidebar` 元件(包含導覽連結:Dashboard、Transactions、Analytics、Settings)。
2. 使用 `lucide-react` 圖標,並使用 `usePathname()` 來判斷當前 active 頁面並套用高亮樣式。
3. 支援響應式 (Responsive):
- 桌面端 (md 以上):左側固定寬度 (64px/240px) 的 Sidebar。
- 移動端 (md 以下):隱藏左側 Sidebar,隱藏選單收納進 shadcn/ui 的 `<Sheet>` 漢堡選單中。
4. 包含 Header:包含 Logo、頁面標題與 User Profile 區域。
5. 請遵從 React Server Component (RSC) 與 Client Component 的分界原則。
AI 給出了側邊欄與頁面配置的初步程式碼:
// components/Sidebar.tsx
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { LayoutDashboard, ReceiptText, PieChart, Settings } from "lucide-react"
import { cn } from "@/lib/utils"
const navItems = [
{ label: "Dashboard", href: "/dashboard", icon: LayoutDashboard },
{ label: "Transactions", href: "/transactions", icon: ReceiptText },
{ label: "Analytics", href: "/analytics", icon: PieChart },
{ label: "Settings", href: "/settings", icon: Settings },
]
export function Sidebar() {
const pathname = usePathname()
return (
<aside className="w-64 bg-background border-r h-screen hidden md:block">
<div className="p-6 font-bold text-lg">Smart Finance</div>
<nav className="space-y-2 px-4">
{navItems.map((item) => {
const Icon = item.icon
const isActive = pathname === item.href
return (
<Link
key={item.href}
href={item.href}
className={cn(
"flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium",
isActive ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-accent"
)}
>
<Icon className="h-4 w-4" />
{item.label}
</Link>
)
})}
</nav>
</aside>
)
}
AI 給出的邏輯結構看似合理,但在真實的 Next.js 開發流程中,這份程式碼踩到了 三大雷區!
AI 使用了 shadcn/ui 的 <Sheet> 來實作移動端選單,但它忘記處理路由跳轉時的狀態關閉!使用者在手機上點擊「Transactions」後,頁面雖然換了,但選單 Drawer 依然硬生生地擋在畫面上,體驗極差。
AI 直接將整頁 app/dashboard/layout.tsx 加上了 "use client",只因為裡面的 Sidebar 需要用到 usePathname()!
這會導致整個 Layout 樹狀結構失去了 React Server Component (RSC) 的伺服器端渲染優勢。
使用 pathname === item.href 當作判斷依據時,一旦使用者進入 /transactions/123(單筆交易明細頁面),側邊欄的 「Transactions」 選單就會失去高亮!
我們進行了以下優化與引導修正:
layout.tsx 保持為 SSR 運作,僅將 Navigation 選單等需要 State 的部分獨立抽取為 "use client" 元件。pathname.startsWith(item.href) 來維持子頁面的選單高亮狀態。useEffect 監聽 pathname 來自動切換 open 狀態。修正後的精華程式碼:
// components/Navigation/NavItem.tsx
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
interface NavItemProps {
href: string
label: string
icon: React.ElementType
onSelect?: () => void
}
export function NavItem({ href, label, icon: Icon, onSelect }: NavItemProps) {
const pathname = usePathname()
// 解決子路由高亮失效的問題(精確比對根目錄,前綴比對子頁面)
const isActive = href === "/dashboard"
? pathname === "/dashboard"
: pathname.startsWith(href)
return (
<Link
href={href}
onClick={onSelect}
className={cn(
"flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-colors",
isActive
? "bg-primary text-primary-foreground font-semibold shadow-sm"
: "text-muted-foreground hover:bg-muted hover:text-foreground"
)}
>
<Icon className={cn("h-4 w-4", isActive ? "text-primary-foreground" : "text-muted-foreground")} />
{label}
</Link>
)
}
"use client"。但身為工程師,我們必須時刻保持警覺——把 "use client" 推到元件樹(Component Tree)的最葉端,讓主體架構保持 SSR 渲染,才能確保最佳的載入速度與 SEO 效能。