# 性能優化指南 ## 🚀 性能目標 | 指標 | 目標 | 工具 | |------|------|------| | **First Contentful Paint (FCP)** | < 1.8s | Lighthouse | | **Largest Contentful Paint (LCP)** | < 2.5s | Web Vitals | | **First Input Delay (FID)** | < 100ms | Web Vitals | | **Cumulative Layout Shift (CLS)** | < 0.1 | Web Vitals | | **Time to Interactive (TTI)** | < 3.8s | Lighthouse | | **Bundle Size** | < 200KB (gzipped) | Webpack Bundle Analyzer | ## 📦 打包優化 ### 1. 代碼分割策略 ```typescript // next.config.js module.exports = { experimental: { optimizeCss: true, }, webpack: (config, { isServer }) => { if (!isServer) { config.optimization.splitChunks = { chunks: 'all', cacheGroups: { default: false, vendors: false, framework: { name: 'framework', chunks: 'all', test: /(? 160000 && /node_modules[/\\]/.test(module.identifier()) }, name(module) { const hash = crypto.createHash('sha1') hash.update(module.identifier()) return hash.digest('hex').substring(0, 8) }, priority: 30, minChunks: 1, reuseExistingChunk: true, }, commons: { name: 'commons', chunks: 'all', minChunks: 2, priority: 20, }, shared: { name(module, chunks) { return crypto .createHash('sha1') .update(chunks.reduce((acc, chunk) => acc + chunk.name, '')) .digest('hex') }, priority: 10, minChunks: 2, reuseExistingChunk: true, }, }, } } return config }, } ``` ### 2. 動態導入 ```typescript // 使用動態導入減少初始載入 import dynamic from 'next/dynamic' // 延遲載入大型組件 const FlashcardEditor = dynamic( () => import('@/components/FlashcardEditor'), { loading: () => , ssr: false, // 客戶端渲染 } ) // 條件載入 const AdminPanel = dynamic( () => import('@/components/AdminPanel'), { loading: () =>
Loading admin panel...
, } ) // 路由級別代碼分割 export default function Page() { const [showEditor, setShowEditor] = useState(false) return (
{showEditor && }
) } ``` ### 3. Tree Shaking ```typescript // utils/index.ts // ❌ 錯誤:導出所有 export * from './helpers' // ✅ 正確:具名導出 export { formatDate, parseJSON } from './helpers' // 使用時 // ❌ 錯誤:導入整個庫 import * as utils from '@/utils' // ✅ 正確:只導入需要的 import { formatDate } from '@/utils' ``` ## 🖼️ 圖片優化 ### 1. Next.js Image 優化 ```typescript // components/OptimizedImage.tsx import Image from 'next/image' export function OptimizedImage({ src, alt }: { src: string; alt: string }) { return ( {alt} ) } ``` ### 2. 響應式圖片 ```typescript // utils/imageOptimization.ts export function generateImageSizes(src: string) { const sizes = [640, 750, 828, 1080, 1200, 1920, 2048, 3840] return { src, srcSet: sizes .map(size => `${src}?w=${size} ${size}w`) .join(', '), sizes: '(max-width: 640px) 100vw, (max-width: 1200px) 50vw, 33vw', } } ``` ## ⚡ 渲染優化 ### 1. React 組件優化 ```typescript // components/FlashcardList.tsx import { memo, useMemo, useCallback } from 'react' // 使用 memo 避免不必要的重新渲染 const FlashcardItem = memo(({ card, onSelect }: FlashcardItemProps) => { return (
onSelect(card.id)}> {card.word}
) }, (prevProps, nextProps) => { // 自定義比較函數 return prevProps.card.id === nextProps.card.id && prevProps.card.word === nextProps.card.word }) export function FlashcardList({ cards }: { cards: Card[] }) { // 使用 useCallback 避免函數重新創建 const handleSelect = useCallback((id: string) => { console.log('Selected:', id) }, []) // 使用 useMemo 快取計算結果 const sortedCards = useMemo(() => { return [...cards].sort((a, b) => a.word.localeCompare(b.word)) }, [cards]) return (
{sortedCards.map(card => ( ))}
) } ``` ### 2. 虛擬滾動 ```typescript // components/VirtualList.tsx import { FixedSizeList } from 'react-window' export function VirtualFlashcardList({ cards }: { cards: Card[] }) { const Row = ({ index, style }: { index: number; style: any }) => (
) return ( {Row} ) } ``` ### 3. Suspense 與並行渲染 ```typescript // app/dashboard/page.tsx import { Suspense } from 'react' export default function Dashboard() { return (
}> {/* 異步組件 */} }> {/* 異步組件 */} }> {/* 異步組件 */}
) } ``` ## 🗄️ 數據獲取優化 ### 1. 數據預載入 ```typescript // app/flashcards/[id]/page.tsx import { preloadFlashcard } from '@/lib/api/flashcards' export default async function FlashcardPage({ params }: { params: { id: string } }) { // 預載入相關數據 preloadFlashcard(params.id) preloadRelatedFlashcards(params.id) const flashcard = await getFlashcard(params.id) return } ``` ### 2. 並行數據獲取 ```typescript // hooks/useParallelFetch.ts export function useDashboardData() { const [stats, flashcards, progress] = useQueries({ queries: [ { queryKey: ['stats'], queryFn: fetchUserStats, staleTime: 5 * 60 * 1000, // 5 分鐘 }, { queryKey: ['recent-flashcards'], queryFn: fetchRecentFlashcards, staleTime: 60 * 1000, // 1 分鐘 }, { queryKey: ['progress'], queryFn: fetchLearningProgress, staleTime: 10 * 60 * 1000, // 10 分鐘 }, ], }) return { stats: stats.data, flashcards: flashcards.data, progress: progress.data, isLoading: stats.isLoading || flashcards.isLoading || progress.isLoading, } } ``` ### 3. 無限滾動優化 ```typescript // hooks/useInfiniteFlashcards.ts export function useInfiniteFlashcards() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, } = useInfiniteQuery({ queryKey: ['flashcards'], queryFn: ({ pageParam = 0 }) => fetchFlashcards({ page: pageParam, limit: 20 }), getNextPageParam: (lastPage, pages) => lastPage.nextCursor, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, }) const allFlashcards = useMemo( () => data?.pages.flatMap(page => page.items) ?? [], [data] ) return { flashcards: allFlashcards, loadMore: fetchNextPage, hasMore: hasNextPage, isLoading: isFetchingNextPage, } } ``` ## 🎨 CSS 優化 ### 1. Critical CSS ```typescript // pages/_document.tsx import { getCssText } from '@/stitches.config' export default function Document() { return (