You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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官方工具:

  1. 在项目根目录执行gitbook init docs初始化Gitbook
  2. 在docs目录编写内容后,执行gitbook build docs生成静态文件到_book文件夹
  3. 将_book重命名为blog,移动到React项目的public目录下
    注意:此方式下/blog会直接加载静态文件,绕过React路由,若需共享导航栏,需修改Gitbook模板或用iframe嵌入。

内容的提问来源于stack exchange,提问作者cspurposesonly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:17:56