Next.js 博客项目开发
使用 Next.js App Router 构建技术博客的核心方案:MDX 内容管理实现写作与渲染分离,Metadata API 实现 SEO 优化,next-themes 实现深色模式,@next/bundle-analyzer 实现性能分析。
技术选型
| 功能 | 方案 | 说明 |
|---|---|---|
| 内容管理 | MDX + 文件系统 | 无需 CMS,Git 管理内容 |
| 样式 | Tailwind CSS | 原子化 CSS,暗色模式原生支持 |
| SEO | Next.js Metadata API | 动态生成 OG Image、结构化数据 |
| 深色模式 | next-themes | 跟随系统/手动切换,无闪烁 |
| 国际化 | next-intl | 路由级 i18n |
| 性能分析 | @next/bundle-analyzer | 可视化打包体积 |
| 评论 | Giscus / Twikoo | 基于 GitHub Discussions |
项目结构
code
blog/
├── app/
│ ├── layout.tsx # 根布局(主题 Provider)
│ ├── page.tsx # 首页(文章列表)
│ ├── blog/
│ │ ├── page.tsx # 文章列表页
│ │ └── [slug]/
│ │ └── page.tsx # 文章详情页
│ └── about/
│ └── page.tsx # 关于页
├── content/
│ └── posts/ # MDX 文章
│ ├── hello-world.mdx
│ └── nextjs-app-router.mdx
├── components/
│ ├── ThemeToggle.tsx # 主题切换按钮
│ ├── PostCard.tsx # 文章卡片
│ └── MDXComponents.tsx # MDX 自定义组件
├── lib/
│ └── posts.ts # 文章读取/解析工具
└── next.config.tsMDX 内容管理
文章 Frontmatter 规范
mdx
---
title: "Next.js App Router 深度指南"
date: "2025-06-15"
tags: ["Next.js", "React", "SSR"]
summary: "全面解析 App Router 的路由、数据获取和渲染策略"
cover: "/images/nextjs-guide.png"
---
正文内容...支持 JSX 组件嵌入。文章解析工具
typescript
// lib/posts.ts
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const postsDir = path.join(process.cwd(), 'content/posts');
export interface Post {
slug: string;
title: string;
date: string;
tags: string[];
summary: string;
}
export function getAllPosts(): Post[] {
const files = fs.readdirSync(postsDir).filter((f) => f.endsWith('.mdx'));
return files
.map((filename) => {
const slug = filename.replace(/\.mdx$/, '');
const raw = fs.readFileSync(path.join(postsDir, filename), 'utf-8');
const { data } = matter(raw);
return { slug, ...data } as Post;
})
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
}
export function getPostBySlug(slug: string) {
const raw = fs.readFileSync(path.join(postsDir, `${slug}.mdx`), 'utf-8');
const { data, content } = matter(raw);
return { metadata: data as Post, content };
}SEO 优化
动态 Metadata
tsx
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
import { getPostBySlug } from '@/lib/posts';
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const { metadata } = getPostBySlug(params.slug);
return {
title: `${metadata.title} | My Blog`,
description: metadata.summary,
openGraph: {
title: metadata.title,
description: metadata.summary,
type: 'article',
publishedTime: metadata.date,
tags: metadata.tags,
images: [{ url: metadata.cover }],
},
twitter: {
card: 'summary_large_image',
title: metadata.title,
description: metadata.summary,
},
};
}Sitemap 与 RSS
typescript
// app/sitemap.ts
import { getAllPosts } from '@/lib/posts';
export default function sitemap() {
const posts = getAllPosts().map((post) => ({
url: `https://myblog.com/blog/${post.slug}`,
lastModified: new Date(post.date),
changeFrequency: 'monthly' as const,
priority: 0.8,
}));
return [
{ url: 'https://myblog.com', lastModified: new Date(), priority: 1 },
...posts,
];
}深色模式
next-themes 集成
tsx
// app/layout.tsx
import { ThemeProvider } from 'next-themes';
export default function RootLayout({ children }) {
return (
<html lang="zh" suppressHydrationWarning>
<body>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}tsx
// components/ThemeToggle.tsx
'use client';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
export default function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
{theme === 'dark' ? '☀️' : '🌙'}
</button>
);
}性能分析
Bundle Analyzer 配置
typescript
// next.config.ts
import bundleAnalyzer from '@next/bundle-analyzer';
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});
export default withBundleAnalyzer({
// 其他配置
});bash
# 运行分析
ANALYZE=true npm run build性能优化清单
| 优化项 | 措施 | 效果 |
|---|---|---|
| 图片 | next/image + WebP/AVIF | 减少 50%+ 图片体积 |
| 字体 | next/font 本地加载 | 消除 FOUT/FOIT |
| 代码分割 | 按路由自动分割 + 动态 import | 减少首屏 JS |
| 缓存 | ISR + CDN 边缘缓存 | TTFB < 100ms |
| 组件 | Server Component 优先 | 减少客户端 JS |
评论系统(Giscus)
tsx
// components/Comments.tsx
'use client';
import { useTheme } from 'next-themes';
export default function Comments({ slug }: { slug: string }) {
const { theme } = useTheme();
return (
<div
className="giscus"
data-repo="username/blog-comments"
data-repo-id="R_xxx"
data-category="Announcements"
data-category-id="DIC_xxx"
data-mapping="specific"
data-term={slug}
data-theme={theme === 'dark' ? 'dark' : 'light'}
data-lang="zh-CN"
/>
);
}