在Day23中,我們成功完成了全站Dashboard數據儀表板,整合了全站KPI指標與Recharts雙維度視覺化圖表。
今天(Day24),我們將聚焦於產品邁向Production生產環境前最關鍵的一環——效能優化、SEO動態社交卡片與PWA離線支援!
我們將針對VibePulse進行全方位的效能調優與PWA改造,目標是在GoogleLighthouse測試中拿下綠標乃至滿分:
CodeSplitting與DynamicImports:將龐大的圖表庫(Recharts)與Markdown編輯器進行動態載入,大幅降低首屏InitialBundleSize。
DynamicOGMetadata(@vercel/og):利用Next.jsImageResponse自動為每個技能頁面生成獨一無二的動態社交分享卡片(OpenGraphImage)。
PWA漸進式Web應用(next-pwa):配置WebAppManifest與ServiceWorker,讓VibePulse支援手機/桌面端安裝至主畫面與離線快取。
1.步驟1:動態載入重型組件(DynamicImports)
像Recharts或ByteMD這類包含大量DOM操作與Math運算的第三方套件,若在首屏直接同步import,會大幅拉長FirstContentfulPaint(FCP)與TimetoInteractive(TTI)。
我們利用Next.js的next/dynamic進行懶載入(LazyLoading)與SSR關閉:
// components/dashboard/DynamicCharts.tsx
'use client';
import dynamic from 'next/dynamic';
import { Skeleton } from '@/components/ui/Skeleton';
// 動態載入重型圖表組件,關閉 SSR 避免 Recharts 水合 (Hydration) 不一致
export const DynamicCategoryRadarChart = dynamic(
() => import('./CategoryRadarChart').then((mod) => mod.CategoryRadarChart),
{
ssr: false,
loading: () => <Skeleton className="w-full h-80 rounded-2xl bg-slate-900/60" />,
}
);
export const DynamicPracticeTrendChart = dynamic(
() => import('./PracticeTrendChart').then((mod) => mod.PracticeTrendChart),
{
ssr: false,
loading: () => <Skeleton className="w-full h-80 rounded-2xl bg-slate-900/60" />,
}
);
在app/dashboard/page.tsx中直接替換為DynamicCategoryRadarChart,即可立刻從主Bundle中抽離數百KB的JavaScript!
2.步驟2:動態OpenGraph社交卡片生成(@vercel/og)
當使用者分享技能連結(如/skills/[id])至Line、Discord或Twitter時,高質感的動態封面卡片能大幅提升點擊率。
Next.js內建的ImageResponse允許我們使用JSX/HTML/TailwindCSS語法在EdgeRuntime即時渲染PNG圖片:
在技能詳細頁目錄下新增app/skills/[id]/opengraph-image.tsx:
// app/skills/[id]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { prisma } from '@/lib/prisma';
export const runtime = 'edge';
export const alt = 'VibePulse 技能學習卡片';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export default async function Image({ params }: { params: { id: string } }) {
const skill = await prisma.skill.findUnique({
where: { id: params.id },
select: { title: true, category: true, proficiency: true },
});
const title = skill?.title || '未知技能';
const category = skill?.category || 'General';
const proficiency = skill?.proficiency || 0;
return new ImageResponse(
(
<div
style={{
background: 'linear-gradient(to bottom right, #0f172a, #020617)',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'space-between',
padding: '80px',
color: '#f8fafc',
fontFamily: 'sans-serif',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<div
style={{
padding: '8px 20px',
borderRadius: '9999px',
background: 'rgba(6, 182, 212, 0.2)',
border: '1px solid rgba(6, 182, 212, 0.4)',
color: '#22d3ee',
fontSize: '24px',
fontWeight: 'bold',
}}
>
{category}
</div>
<span style={{ color: '#64748b', fontSize: '24px' }}>VibePulse Skill Share</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
<h1 style={{ fontSize: '64px', fontWeight: 900, color: '#f8fafc', margin: 0 }}>
{title}
</h1>
<p style={{ fontSize: '28px', color: '#94a3b8', margin: 0 }}>
當前掌握度進度:{proficiency}%
</p>
</div>
{/* 進度條 */}
<div
style={{
width: '100%',
height: '16px',
background: '#1e293b',
borderRadius: '8px',
overflow: 'hidden',
display: 'flex',
}}
>
<div
style={{
width: `${proficiency}%`,
height: '100%',
background: 'linear-gradient(to right, #06b6d4, #3b82f6)',
}}
/>
</div>
</div>
),
{ ...size }
);
}
3.步驟3:配置PWA(ProgressiveWebApp)
讓VibePulse能安裝至手機桌面,並在無網路環境下展示快取頁面。
A.安裝@ducanh2912/next-pwa
npm install @ducanh2912/next-pwa
B.設定next.config.mjs
// next.config.mjs
import withPWAInit from '@ducanh2912/next-pwa';
const withPWA = withPWAInit({
dest: 'public',
disable: process.env.NODE_ENV === 'development', // 開發環境關閉 Service Worker
register: true,
skipWaiting: true,
});
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
export default withPWA(nextConfig);
C.建立public/manifest.json
{
"name": "VibePulse 技能圖譜與學習追蹤",
"short_name": "VibePulse",
"description": "個人化技能圖譜、Markdown 學習筆記與數據可視化打卡工具",
"start_url": "/dashboard",
"display": "standalone",
"background_color": "#020617",
"theme_color": "#06b6d4",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
D.在app/layout.tsx中注入Manifest與ThemeColor
// app/layout.tsx
import type { Metadata, Viewport } from 'next';
export const metadata: Metadata = {
title: 'VibePulse - 技能圖譜與學習追蹤',
description: '個人化技能圖譜與數據打卡工具',
manifest: '/manifest.json',
appleWebApp: {
capable: true,
statusBarStyle: 'black-translucent',
title: 'VibePulse',
},
};
export const viewport: Viewport = {
themeColor: '#06b6d4',
width: 'device-width',
initialScale: 1,
maximumScale: 1,
};
今天我們完成了VibePulse生產環境級別的最後優化:
CodeSplitting瘦身:使用next/dynamic隔離Recharts與大型組件,減少JS首屏下載體積。
動態社交分享卡片:整合@vercel/ogEdgeAPI,每次分享技能時自動即時生成包含實時熟練度與類別的PNG視覺圖卡。
PWA支援:配置WebAppManifest與ServiceWorker快取,支援手機/桌面獨立視窗安裝與離線載入。