使用 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
克隆项目并启动开发服务器后,http://localhost:3000/
在浏览器中导航至以查看您正在处理的内容。
如你所见,目前它非常简单。如果你在代码编辑器中查看该项目,你将看到以下目录结构:
components/
data/
pages/
styles/
项目结构
我们来看看这个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,"
},
}
}
您将看到现在有一个用组件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来制作示例照片,而Cupcake、Hipsum或Sagan 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.
另外,在根目录创建一个public
文件夹。这是保存图片的地方。
处理 Markdown 文件
接下来,您需要安装一些用于处理 Markdown 文件的包。
$ yarn add raw-loader gray-matter react-markdown
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
},
}
页面和动态路由
现在,您已准备好在项目中使用 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,
}
}
请注意,在这个例子中,我们正在利用
gray-matter
并ReactMarkdown
正确处理 YAML 前言和 Markdown 正文。
简要看一下它的工作原理:当你导航到动态路由(例如 )时,会http://localhost:3000/blog/julius-caesar
向 BlogTemplate 组件传递一个对象。当函数被调用时,该对象会通过上下文传入。你获取该 slug 值,然后在目录中搜索包含相同文件名的文件。获取该文件的数据后,你会解析 Markdown 正文中的 frontmatter 并返回数据。这些数据作为 props 传递给组件,然后组件可以根据需要渲染这些数据。pages/blog/[slug].js
params
{ slug: ‘julius-caesar’ }
getStaticProps
params
posts
BlogTemplate
获取静态路径
此时,您可能对 比较熟悉getStaticProps
,但这个函数应该看起来很新—— getStaticPaths
。由于此模板使用动态路由,因此您需要为每个博客定义一个路径列表,这样所有页面将在构建时静态呈现。
在从 返回的对象中getStaticPaths
,需要两个键:paths
和fallback
。paths
应该返回一个包含路径名和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,
},
}
}
这看起来可能有点复杂,但让我们一步一步来。您可以参考这篇博客获取原始代码。它使用了 Webpack 提供的函数require.context(),该函数允许您基于三个参数创建自己的“上下文”:
- 要匹配的目录。
- 用于包含或排除子目录的布尔标志。
- 用于匹配文件的正则表达式。
require.context(directory, (useSubdirectories = false), (regExp = /^\.\//))
创建“上下文”允许我们创建一个空间,您可以在其中从特定目录中挑选出与正则表达式匹配的所有文件,并将它们处理成可管理的格式,作为要渲染的道具提供回组件。
现在您已经拥有了所有博客数据,请将其作为 prop 传递给BlogList
组件。
const Index = props => {
return (
<Layout
pathname="/"
siteTitle={props.title}
siteDescription={props.description}
>
<section>
<BlogList allBlogs={props.allBlogs} />
</section>
</Layout>
)
}
export default Index
然后,您可以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