使用 Next.js 创建 Markdown 博客

2025-05-28

使用 Next.js 创建 Markdown 博客

02.08.22:此帖子已更新,使用 Next 12 及其最新功能。

Next.js 是一个用于开发 Web 应用程序的 React “元框架”(基于框架构建的框架)。Next.js 因其自举式 React 环境(类似于create-react-app)以及用于编写后端代码的简单、基于文件的路由功能,已成为 Web 开发者的热门选择。

Next.js 简单灵活。与功能齐全的静态网站生成器相比,它在应用或网站的实现过程中,对开发者的限制更少。正因如此,本文仅分享一种构建简单 Markdown 博客的方法。请采纳其中有用的内容,忽略其他。


如果您想跳过前面的内容并参考启动器的最终版本,请随时查看完成的实现

克隆启动器

让我们开始吧。我提供了一个基本的入门指南,可以作为本教程的起点。您可以克隆该项目或在GitHub上查看以供参考。

// clone the repo from your terminal
$ git clone https://github.com/perkinsjr/nextjs-starter-boilerplate my-nextjs-blog

// install the dependencies
$ cd my-nextjs-blog
$ yarn install

// start up the dev server
$ yarn dev
Enter fullscreen mode Exit fullscreen mode

克隆项目并启动开发服务器后,http://localhost:3000/在浏览器中导航至以查看您正在处理的内容。

NextJS 入门博客

如你所见,目前它非常简单。如果你在代码编辑器中查看该项目,你将看到以下目录结构:

components/
data/
pages/
styles/
Enter fullscreen mode Exit fullscreen mode

项目结构

我们来看看这个pages/index.js文件:

const Index = props => {
  return (
    <Layout
      pathname="/"
      siteTitle={props.title}
      siteDescription={props.description}
    >
      <section>
        <BlogList />
      </section>
    </Layout>
  )
}

export default Index

export async function getStaticProps() {
  const configData = await import(`../data/config.json`)
  return {
    props: {
      title: "configData.title,"
      description: "configData.description,"
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

您将看到现在有一​​个用组件Layout包装的组件——这些都是迄今为止呈现我们的小启动器的所有部分。<section>BlogList

数据处理

Next.js会预渲染每个页面,这意味着它会预先生成页面的 HTML。从Next.js 9.3开始,预渲染页面有两种方式:静态生成或服务器端渲染 (SSR)。Next.js 的独特之处在于,您可以根据项目使用其中任何一种方法。

在本博客中,您将实现静态生成,这意味着每个路由的 HTML 页面将在构建时生成。静态生成允许页面被 CDN 缓存,从而提高性能。

获取静态属性

在初始示例中index.js,请注意组件下方的 的使用getStaticProps。此函数允许您获取数据并将其作为 props 返回给页面组件。页面将在构建时使用 中返回对象的 props进行渲染getStaticProps

这是您在 Next 中检索页面级数据的关键所在。您可以使用getStaticProps它从外部 API 获取数据,或者像本例所示,获取本地数据源。

注意:此方法仅适用于pages/目录中定义的组件(即组件)。您无法将此方法应用于子组件,但您可以将接收到的数据传递给这些子组件,就像您在上面的示例中page看到的那样。Layout

Layout正在传递诸如网站标题和描述之类的属性。如果您查看 中的数据data/config.json,您将看到这些属性所引用的值。继续将网站标题更改为您的项目名称,然后观察其在标题中的更新。

布局和样式

简单来说,该Layout组件的目的是为网站的每个页面提供视觉框架。它通常包含一些在大多数或所有页面上都会显示的导航或页眉,以及一个页脚元素。在本例中,你只需要一个包含网站标题的页眉。

其中Layout,有一个Meta组件包含所有全局样式以及出于 SEO 或可访问性目的需要添加到head网站中的所有内容。请注意,组件的使用Layout并非 Next.js 独有;您还会发现它在 Gatsby 网站中也广泛使用。

您可能注意到Layout组件中使用了组件级 CSS。Next.js开箱即用,支持 组件级 CSS。使用起来非常直观。所有样式都限定在组件范围内,这意味着您不必担心意外覆盖其他地方的样式。

请注意,全局样式和字体是在globals.css目录中处理的styles,因此如果您想更改字体或添加更多全局样式,您可以在此处添加。

添加 Posts 目录

现在您已经熟悉了项目结构和 Next.js 基础知识,让我们开始添加各个部分来启动和运行 Markdown 博客。

首先,在项目根目录下添加一个名为 的新文件夹posts。你可以在这里添加所有 Markdown 博客文章。如果你还没有准备好内容,只需添加一些虚拟博客文章即可。我喜欢使用Unsplash来制作示例照片,而CupcakeHipsumSagan Ipsum是我首选的文本生成器——它们能让事情变得有趣。

以下是一个填充博客文章的示例,其中包含一些常用的前言值。

---
title: A trip to Iceland
author: 'Watson & Crick '
date: '2019-07-10T16:04:44.000Z'
hero_image: /norris-niman-iceland.jpg
---
Brain is the seed of intelligence something incredible is waiting to be known.
Enter fullscreen mode Exit fullscreen mode

另外,在根目录创建一个public文件夹。这是保存图片的地方。

处理 Markdown 文件

接下来,您需要安装一些用于处理 Markdown 文件的包。

$ yarn add raw-loader gray-matter react-markdown
Enter fullscreen mode Exit fullscreen mode

Raw Loader将处理你的 Markdown 文件。Gray Matter将解析你的 yaml frontmatter 值。React Markdown将解析并渲染你的 Markdown 文件的主体部分。

添加 Next.js 配置

现在您已经安装了处理 Markdown 所需的一些软件包,您需要在项目根目录下raw-loader创建一个next.config.js文件来配置其使用。在这个文件中,您将处理 Webpack 的所有自定义配置,包括路由、构建和运行时配置、导出选项等等。在您的用例中,您只需添加一条用于raw-loader处理所有 Markdown 文件的 Webpack 规则即可。

//next.config.js
module.exports = {
  webpack: function(config) {
    config.module.rules.push({
      test: /\.md$/,
      use: 'raw-loader',
    })
    return config
  },
}
Enter fullscreen mode Exit fullscreen mode

页面和动态路由

现在,您已准备好在项目中使用 Markdown 文件。让我们开始编写一个博客模板页面,该页面将以 的形式呈现这些 Markdown 文件的内容posts

需要一些背景知识,pages目录在 Next.js 中很特殊。.js此目录中的每个文件都会响应匹配的 HTTP 请求。例如,当请求主页(“/”)时,pages/index.js将从中导出的组件进行渲染。如果您希望您的网站包含一个位于 的页面/about,只需创建一个名为 的文件pages/about.js

这对于静态页面来说非常棒,但您希望所有博客文章都基于同一个模板构建,并从每个 Markdown 文件中获取不同的数据。这意味着您需要某种动态路由,以便使用相同模板的不同博客文章拥有“美观”的 URL 和各自的页面。

Next.js 中的动态路由通过文件名中的方括号 标识[]。在这些方括号内,您可以将查询参数传递给页面组件。例如,我们在pages名为 的新文件夹中创建一个名为blog的新文件夹,然后在该博客文件夹中添加一个新文件[slug].js,您可以使用作为此slug参数传递的任何内容来动态访问数据。因此,如果您访问http://localhost:3000/blog/julius-caesar,则页面组件返回的任何内容都[slug].js将被渲染,并且可以访问该“slug”查询参数,即“julius-caesar”。

获取博客模板的 Markdown 数据

通过动态路由,您可以通过传入博客文章的文件名来利用此 slug 参数,然后通过从相应的 Markdown 文件中获取数据getStaticProps

import matter from 'gray-matter'
import ReactMarkdown from 'react-markdown'
import Layout from '../../components/Layout'

export default function BlogTemplate(props) {
  // Render data from `getStaticProps`
  return (
    <Layout siteTitle={props.siteTitle}>
      <article>
        <h1>{props.frontmatter.title}</h1>
        <div>
          <ReactMarkdown source={props.markdownBody} />
        </div>
      </article>
    </Layout>
  )
}

export async function getStaticProps({ ...ctx }) {
  const { slug } = ctx.params
  const content = await import(`../../posts/${slug}.md`)
  const config = await import(`../../data/config.json`)
  const data = matter(content.default)

  return {
    props: {
      siteTitle: config.title,
      frontmatter: data.data,
      markdownBody: data.content,
    },
  }
}

export async function getStaticPaths() {
  //get all .md files in the posts dir
  const blogs = glob.sync('posts/**/*.md')

  //remove path and extension to leave filename only
  const blogSlugs = blogs.map(file =>
    file
      .split('/')[1]
      .replace(/ /g, '-')
      .slice(0, -3)
      .trim()
  )

  // create paths with `slug` param
  const paths = blogSlugs.map(slug => `/blog/${slug}`)

  return {
    paths,
    fallback: false,
  }
}
Enter fullscreen mode Exit fullscreen mode

请注意,在这个例子中,我们正在利用gray-matterReactMarkdown正确处理 YAML 前言和 Markdown 正文。

简要看一下它的工作原理:当你导航到动态路由(例如 )时,http://localhost:3000/blog/julius-caesar向 BlogTemplate 组件传递一个对象。当函数被调用时,该对象会通过上下文传入。你获取该 slug 值,然后在目录中搜索包含相同文件名的文件。获取该文件的数据后,你会解析 Markdown 正文中的 frontmatter 并返回数据。这些数据作为 props 传递给组件,然后组件可以根据需要渲染这些数据。pages/blog/[slug].jsparams{ slug: ‘julius-caesar’ }getStaticPropsparamspostsBlogTemplate

获取静态路径

此时,您可能对 比较熟悉getStaticProps,但这个函数应该看起来很新—— getStaticPaths。由于此模板使用动态路由,因此您需要为每个博客定义一个路径列表,这样所有页面将在构建时静态呈现。

在从 返回的对象中getStaticPaths需要两个键pathsfallbackpaths应该返回一个包含路径名和params页面名称中使用的任何值的数组。例如, 中使用的“param”/blog/[slug].js是“slug”。您只需要在动态路由中使用getStaticPaths

fallback属性允许您控制未从 返回路径时的行为getStaticPaths。您应该将其设置为 ,false以便未返回的路径将显示 404 页面。

在 Next.js 9.3 发布之前,可以通过 来处理静态导出的路径生成exportPathMap

查看我的初始博客的最终版本中的[slug].js 文件,以了解如何呈现博客数据和应用样式。

获取博客索引的数据

BlogList让我们通过向页面组件添加适当的数据来完成这个简单的博客Index。由于您只能getStaticProps在页面组件中使用,因此您将在组件中获取所有博客数据Index,然后将其作为 prop 传递下去进行BlogList渲染。

// pages/index.js
export async function getStaticProps() {
  const siteConfig = await import(`../data/config.json`)
  //get posts & context from folder
  const posts = (context => {
    const keys = context.keys()
    const values = keys.map(context)

    const data = keys.map((key, index) => {
      // Create slug from filename
      const slug = key
        .replace(/^.*[\\\/]/, '')
        .split('.')
        .slice(0, -1)
        .join('.')
      const value = values[index]
      // Parse yaml metadata & markdownbody in document
      const document = matter(value.default)
      return {
        frontmatter: document.data,
        markdownBody: document.content,
        slug,
      }
    })
    return data
  })(require.context('../posts', true, /\.md$/))

  return {
    props: {
      allBlogs: posts,
      title: siteConfig.default.title,
      description: siteConfig.default.description,
    },
  }
}
Enter fullscreen mode Exit fullscreen mode

这看起来可能有点复杂,但让我们一步一步来。您可以参考这篇博客获取原始代码。它使用了 Webpack 提供的函数require.context(),该函数允许您基于三个参数创建自己的“上下文”:

  • 要匹配的目录。
  • 用于包含或排除子目录的布尔标志。
  • 用于匹配文件的正则表达式。
require.context(directory, (useSubdirectories = false), (regExp = /^\.\//))
Enter fullscreen mode Exit fullscreen mode

创建“上下文”允许我们创建一个空间,您可以在其中从特定目录中挑选出与正则表达式匹配的所有文件,并将它们处理成可管理的格式,作为要渲染的道具提供回组件。

现在您已经拥有了所有博客数据,请将其作为 prop 传递给BlogList组件。

const Index = props => {
  return (
    <Layout
      pathname="/"
      siteTitle={props.title}
      siteDescription={props.description}
    >
      <section>
        <BlogList allBlogs={props.allBlogs} />
      </section>
    </Layout>
  )
}

export default Index
Enter fullscreen mode Exit fullscreen mode

然后,您可以BlogList根据需要循环遍历博客并在组件中渲染列表。您可以参考我的入门指南中的BlogList 组件,了解如何处理这些数据。

后续步骤

检查最终的 repo

设置好博客或作品集网站后,您很可能需要一个内容管理系统来更轻松地编辑和更新帖子或数据。请持续关注我下一篇关于如何使用 TinaCMS 设置此入门版的博客。与此同时,您可以查看我们的文档,或者fork 已完成的 Next+Tina 博客,立即开始使用 TinaCMS。

在哪里可以了解 Tina 的最新动态?

您知道您想成为这个富有创造力、创新性和支持性的开发人员(甚至一些编辑和设计师)社区的一部分,他们每天都在试验和实施 Tina。

Tina 社区 Discord

Tina 有一个Discord社区,里面挤满了 Jamstack 爱好者和 Tina 爱好者。加入后,您将获得以下福利:

  • 获取问题帮助
  • 查找最新的 Tina 新闻和预告
  • 与Tina社区分享你的项目,并谈谈你的经验
  • 聊聊 Jamstack

蒂娜·推特

我们的 Twitter 账号 ( @tina_cms ) 会发布 Tina 的最新功能、改进和预览。如果您在自己开发的项目中标记我们,我们将不胜感激。

文章来源:https://dev.to/tinacms/creating-a-markdown-blog-with-next-js-52hk
PREV
进阶 TypeScript:重新发明 lodash.get
NEXT
使用自定义模板保持 Git 提交消息一致