如何基于React与GH Pages配置书籍式博客页面?
在React + GH Pages中集成Gitbook风格博客的实现方案
核心思路
将Gitbook的Markdown内容(文章、导航配置)嵌入现有React单页应用,通过react-router-dom的BrowserRouter实现/blog(博客首页)和/blog/:postId(单篇文章)的路由结构,同时模拟Gitbook的导航与排版样式。
步骤1:准备博客内容结构
在React项目根目录下创建src/blog-content文件夹,存放所有博客相关文件:
- 创建
SUMMARY.md:定义博客导航结构,示例:# 博客目录 - 初识React Hooks [react-hooks-intro](react-hooks-intro.md) - GH Pages部署技巧 [gh-pages-deploy-tips](gh-pages-deploy-tips.md) - 创建对应文章的Markdown文件(如
react-hooks-intro.md),可在开头添加元数据:--- title: 初识React Hooks date: 2024-05-20 --- 这是关于React Hooks的入门文章...
步骤2:安装必要依赖
在项目根目录执行命令,安装渲染与处理工具:
npm install react-markdown gray-matter react-syntax-highlighter
react-markdown:将Markdown转为React组件gray-matter:提取Markdown文件开头的元数据react-syntax-highlighter:为代码块添加语法高亮
步骤3:配置React路由
修改现有路由配置(如src/App.js),添加博客路由规则:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import BlogHome from './components/BlogHome'; import BlogPost from './components/BlogPost'; // 导入其他现有组件... function App() { return ( <Router> <Routes> {/* 保留原有路由规则 */} <Route path="/blog" element={<BlogHome />} /> <Route path="/blog/:postId" element={<BlogPost />} /> </Routes> </Router> ); } export default App;
步骤4:实现博客首页组件(BlogHome.js)
解析SUMMARY.md并渲染导航列表:
import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import fs from 'fs/promises'; import path from 'path'; function BlogHome() { const [navItems, setNavItems] = useState([]); useEffect(() => { const fetchSummary = async () => { const summaryPath = path.join(process.cwd(), 'src/blog-content/SUMMARY.md'); const content = await fs.readFile(summaryPath, 'utf8'); // 正则匹配导航条目(可根据SUMMARY格式调整) const itemRegex = /- (.*?) \[(.*?)\]\((.*?)\)/g; const items = []; let match; while ((match = itemRegex.exec(content)) !== null) { items.push({ title: match[1], postId: match[3].replace('.md', '') }); } setNavItems(items); }; fetchSummary(); }, []); return ( <div className="blog-home"> <h1>我的技术博客</h1> <div className="nav-list"> {navItems.map((item, index) => ( <Link key={index} to={`/blog/${item.postId}`} className="nav-item"> {item.title} </Link> ))} </div> </div> ); } export default BlogHome;
步骤5:实现单篇文章组件(BlogPost.js)
根据postId加载文章内容,同时保留侧边导航:
import React, { useState, useEffect } from 'react'; import { useParams, Link } from 'react-router-dom'; import ReactMarkdown from 'react-markdown'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'; import grayMatter from 'gray-matter'; import fs from 'fs/promises'; import path from 'path'; function BlogPost() { const { postId } = useParams(); const [postData, setPostData] = useState({ title: '', date: '', content: '' }); const [navItems, setNavItems] = useState([]); useEffect(() => { const fetchPostAndNav = async () => { // 读取导航 const summaryPath = path.join(process.cwd(), 'src/blog-content/SUMMARY.md'); const summaryContent = await fs.readFile(summaryPath, 'utf8'); const itemRegex = /- (.*?) \[(.*?)\]\((.*?)\)/g; const items = []; let match; while ((match = itemRegex.exec(summaryContent)) !== null) { const currentPostId = match[3].replace('.md', ''); items.push({ title: match[1], postId: currentPostId, isActive: currentPostId === postId }); } setNavItems(items); // 读取当前文章 const postPath = path.join(process.cwd(), 'src/blog-content', `${postId}.md`); const postContent = await fs.readFile(postPath, 'utf8'); const { data, content } = grayMatter(postContent); setPostData({ title: data.title, date: data.date, content: content }); }; fetchPostAndNav(); }, [postId]); return ( <div className="blog-post-container"> {/* 侧边导航 */} <div className="sidebar"> <Link to="/blog" className="back-link">← 返回博客首页</Link> <div className="nav-list"> {navItems.map((item, index) => ( <Link key={index} to={`/blog/${item.postId}`} className={`nav-item ${item.isActive ? 'active' : ''}`} > {item.title} </Link> ))} </div> </div> {/* 文章内容 */} <div className="post-content"> <h1>{postData.title}</h1> <p className="post-date">{postData.date}</p> <ReactMarkdown components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? ( <SyntaxHighlighter style={tomorrow} language={match[1]} PreTag="div" {...props} > {String(children).replace(/\n$/, '')} </SyntaxHighlighter> ) : ( <code className={className} {...props}> {children} </code> ); } }} > {postData.content} </ReactMarkdown> </div> </div> ); } export default BlogPost;
步骤6:添加Gitbook风格样式
在src/index.css中添加样式,模拟Gitbook布局:
.blog-home { max-width: 800px; margin: 2rem auto; padding: 0 1rem; } .nav-list { margin-top: 2rem; display: flex; flex-direction: column; gap: 1rem; } .nav-item { padding: 0.8rem 1rem; border-radius: 4px; background-color: #f5f5f5; text-decoration: none; color: #333; transition: background-color 0.2s; } .nav-item:hover, .nav-item.active { background-color: #e0e0e0; color: #007bff; } .blog-post-container { display: flex; max-width: 1200px; margin: 2rem auto; gap: 2rem; padding: 0 1rem; } .sidebar { width: 280px; flex-shrink: 0; } .back-link { display: block; margin-bottom: 2rem; text-decoration: none; color: #007bff; } .post-content { flex: 1; line-height: 1.6; } .post-date { color: #666; font-size: 0.9rem; } .post-content h1, .post-content h2, .post-content h3 { margin: 1.5rem 0 1rem; } .post-content p { margin-bottom: 1rem; }
步骤7:部署到GH Pages
执行常规部署命令即可:
npm run deploy
部署完成后,访问https://[username].github.io/blog即可进入博客首页,点击导航可跳转至对应文章。
替代方案:嵌入Gitbook静态产物
若想直接使用Gitbook官方工具:
- 在项目根目录执行
gitbook init docs初始化Gitbook - 在
docs目录编写内容后,执行gitbook build docs生成静态文件到_book文件夹 - 将
_book重命名为blog,移动到React项目的public目录下
注意:此方式下/blog会直接加载静态文件,绕过React路由,若需共享导航栏,需修改Gitbook模板或用iframe嵌入。
内容的提问来源于stack exchange,提问作者cspurposesonly
相关产品推荐
相关产品推荐

