使用 Next.js 15 搭建现代化博客系统
详细介绍如何使用 Next.js 15、TypeScript 和 Tailwind CSS 从零开始搭建一个功能完整的现代化博客系统。
使用 Next.js 15 搭建现代化博客系统
在这篇文章中,我将详细介绍如何使用 Next.js 15、TypeScript 和 Tailwind CSS 从零开始搭建一个功能完整的现代化博客系统。
为什么选择 Next.js?
Next.js 是一个基于 React 的全栈框架,具有以下优势:
- 静态网站生成 (SSG) - 更好的性能和 SEO
- 服务端渲染 (SSR) - 首屏加载速度快
- 文件系统路由 - 简化路由配置
- 内置优化 - 图片优化、代码分割等
- TypeScript 支持 - 开箱即用的类型安全
项目初始化
首先创建一个新的 Next.js 项目:
npx create-next-app@latest my-blog --typescript --tailwind --eslint --app
cd my-blog
安装必要依赖
安装处理 Markdown 文件所需的依赖:
npm install gray-matter remark remark-html date-fns @tailwindcss/typography
npm install -D @types/node
依赖说明
- gray-matter - 解析 Markdown 文件的 frontmatter
- remark - Markdown 处理器
- remark-html - 将 Markdown 转换为 HTML
- date-fns - 日期格式化工具
- @tailwindcss/typography - 美化文章内容的样式
项目结构设计
my-blog/
├── src/
│ ├── app/
│ │ ├── page.tsx # 首页
│ │ ├── posts/[id]/ # 文章详情页
│ │ ├── tags/ # 标签页面
│ │ ├── search/ # 搜索页面
│ │ └── about/ # 关于页面
│ ├── components/ # 组件
│ │ └── Navigation.tsx # 导航组件
│ └── lib/
│ ├── posts.ts # 文章处理逻辑
│ └── utils.ts # 工具函数
├── posts/ # Markdown 文章
└── public/ # 静态资源
核心功能实现
1. 文章数据处理
创建 src/lib/posts.ts
文件:
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { remark } from 'remark'
import html from 'remark-html'
const postsDirectory = path.join(process.cwd(), 'posts')
export interface PostData {
id: string
title: string
date: string
excerpt?: string
tags?: string[]
content: string
}
export function getSortedPostsData(): PostData[] {
const fileNames = fs.readdirSync(postsDirectory)
const allPostsData = fileNames
.filter(fileName => fileName.endsWith('.md'))
.map((fileName) => {
const id = fileName.replace(/\.md$/, '')
const fullPath = path.join(postsDirectory, fileName)
const fileContents = fs.readFileSync(fullPath, 'utf8')
const matterResult = matter(fileContents)
return {
id,
content: matterResult.content,
...(matterResult.data as {
title: string
date: string
excerpt?: string
tags?: string[]
}),
}
})
return allPostsData.sort((a, b) => (a.date < b.date ? 1 : -1))
}
export async function getPostData(id: string): Promise<PostData> {
const fullPath = path.join(postsDirectory, `${id}.md`)
const fileContents = fs.readFileSync(fullPath, 'utf8')
const matterResult = matter(fileContents)
const processedContent = await remark()
.use(html)
.process(matterResult.content)
const contentHtml = processedContent.toString()
return {
id,
content: contentHtml,
...(matterResult.data as {
title: string
date: string
excerpt?: string
tags?: string[]
}),
}
}
2. 导航组件
创建 src/components/Navigation.tsx
:
'use client'
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/navigation'
export default function Navigation() {
const [searchQuery, setSearchQuery] = useState('')
const router = useRouter()
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
if (searchQuery.trim()) {
router.push(`/search?q=${encodeURIComponent(searchQuery.trim())}`)
}
}
return (
<nav className="bg-white/80 backdrop-blur-md border-b border-gray-200 sticky top-0 z-50">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
{/* Logo */}
<Link href="/" className="flex items-center space-x-2">
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">展</span>
</div>
<span className="font-bold text-xl text-gray-900">技术博客</span>
</Link>
{/* Navigation Links */}
<div className="hidden md:flex items-center space-x-8">
<Link href="/" className="text-gray-700 hover:text-blue-600 transition-colors duration-200">
首页
</Link>
<Link href="/tags" className="text-gray-700 hover:text-blue-600 transition-colors duration-200">
标签
</Link>
<Link href="/about" className="text-gray-700 hover:text-blue-600 transition-colors duration-200">
关于
</Link>
</div>
{/* Search */}
<form onSubmit={handleSearch} className="flex items-center">
<input
type="text"
placeholder="搜索文章..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="hidden sm:block w-64 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none"
/>
<button
type="submit"
className="ml-2 p-2 text-gray-600 hover:text-blue-600 transition-colors duration-200"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
</form>
</div>
</div>
</nav>
)
}
高级功能
标签分类系统
实现标签页面,让用户可以按标签浏览文章:
// src/app/tags/page.tsx
export default function TagsPage() {
const allPostsData = getSortedPostsData()
const tagCounts = allPostsData.reduce((acc, post) => {
if (post.tags) {
post.tags.forEach(tag => {
acc[tag] = (acc[tag] || 0) + 1
})
}
return acc
}, {} as Record<string, number>)
// 渲染标签列表...
}
搜索功能
实现全文搜索功能:
// src/app/search/page.tsx
'use client'
export default function SearchPage() {
const [searchResults, setSearchResults] = useState([])
const performSearch = (searchTerm: string) => {
const allPosts = getSortedPostsData()
const results = allPosts.filter(post => {
return post.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.tags?.some(tag => tag.toLowerCase().includes(searchTerm.toLowerCase()))
})
setSearchResults(results)
}
// 渲染搜索结果...
}
部署配置
静态导出配置
在 next.config.ts
中配置静态导出:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
trailingSlash: true,
images: {
unoptimized: true
}
}
export default nextConfig
GitHub Actions 自动部署
创建 .github/workflows/deploy.yml
:
name: Deploy to GitHub Pages
on:
push:
branches: [ main ]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./out
性能优化
1. 图片优化
- 使用 Next.js 的
Image
组件 - 配置适当的图片格式和尺寸
2. 代码分割
- 利用 Next.js 的自动代码分割
- 使用动态导入优化组件加载
3. SEO 优化
- 配置 metadata
- 添加结构化数据
- 生成 sitemap 和 robots.txt
总结
通过这个教程,我们成功搭建了一个功能完整的现代化博客系统,包括:
- ✅ 响应式设计
- ✅ Markdown 文章支持
- ✅ 标签分类系统
- ✅ 全文搜索功能
- ✅ SEO 优化
- ✅ 自动部署
这个博客系统不仅功能丰富,而且性能优秀,是学习 Next.js 和现代前端开发的绝佳实践项目。
下一步
你可以继续扩展以下功能:
- 评论系统
- 文章阅读统计
- 深色模式
- 多语言支持
- RSS 订阅
希望这篇文章对你有所帮助!如果有任何问题,欢迎在评论区讨论。