用 Next.js + MDX + Tailwind CSS + @code-hike/mdx 搭建一个博客网站

Leo
Leo
Cover Image for 用 Next.js + MDX + Tailwind CSS + @code-hike/mdx 搭建一个博客网站
  1. 介绍
  2. 搭建 Next.js Tailwind 环境
  3. 创建项目
  4. 添加 MDX 支持
  5. 创建 MDX 页面
  6. 集成 @code-hike/mdx
  7. 博客内容页面设计
  8. MDX 文件 Meta 数据导出
  9. 主页设计

介绍

这篇文章介绍了如何使用 Next.js,MDX,Tailwind CSS和 @code-hike/mdx 技术来搭建一个博客网站。通过这些技术的结合,可以方便快捷地创建一个支持 Markdown 格式的静态博客网站,并实现优雅的 UI 设计和灵活的布局调整。文章详细介绍了如何在 Next.js 框架下使用MDX来管理页面内容,以及如何使用 Tailwind CSS 来美化页面 UI,最后还介绍了 @code-hike/mdx 这一工具的使用,它可以让读者更方便地学习代码和实现。

搭建 Next.js Tailwind 环境

创建项目

首先,在终端中进入你要创建项目的文件夹,运行以下命令创建一个新的 Next.js 项目:

Terminal
Copy

npx create-next-app@latest my-project --typescript --eslint
cd my-project

安装Tailwind

Terminal
Copy

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

根据Tailwind的文档说明,将所有模板文件的路径添加到配置文件tailwind.config.js

tailwind.config.js
Copy

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

将以下内容添加到styles/globals.css 文件:

styles/globals.css
Copy

@tailwind base;
@tailwind components;
@tailwind utilities;

使用Tailwind 样式修改index.tsx 文件:

index.tsx
Copy

export default function Home() {
return (
<h1 className="text-3xl font-bold underline">
Hello world!
</h1>
)
}

好了,到这一步我们已经创建了一个支持TailWind 的 NextJs项目,可以在终端输入如下命令以后,在浏览器中打开 http://localhost:3000 就可以看到我们修改好的页面。

Terminal
Copy

npm run dev

添加 MDX 支持

在项目的根目录下运行以下命令安装 mdx 相关库:

Terminal
Copy

npm install @mdx-js/loader @mdx-js/loader @mdx-js/mdx @mdx-js/react @next/mdx

添加以下配置信息到 next.config.js:

next.config.js
Copy

const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
remarkPlugins: [],
},
});
module.exports = withMDX({
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
});

创建 MDX 页面

根目录下创建一个 pages/posts 文件夹,并在其中添加一个名为 example.mdx 的文件。文件内容可以如下:

example.mdx
Copy

---
title: '示例文章'
date: '2023-03-22'
---
# 示例文章
这是一篇示例文章。
## 代码演示
下面是一个代码演示:
# 用 Next.js + MDX + Tailwind CSS + @code-hike/mdx 搭建一个 SSG 博客网站
## 介绍
本文介绍如何使用 Next.js、MDX、Tailwind CSS 和 @code-hike/mdx 创建一个静态博客网站,并部署到 Vercel 上。
## 搭建 Next.js 环境
// 省略部分代码...

保存文件以后在浏览器中打开 http://localhost:3000/posts/example 就可以看到新建的博客内容

集成 @code-hike/mdx

在项目的根目录下运行以下命令安装 @code-hike/mdx

Terminal
Copy

npm install @code-hike/mdx

安装完成以后,需要将@code-hike/mdx/集成到我们的项目中,将pages/_app.tsx 做如下修改:

pages/_app.tsx
Copy

import "@code-hike/mdx/dist/index.css"
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}

为代码高亮选择主题,配置是否显示拷贝按钮,是否显示行号等:

next.config.js
Copy

const theme = require('shiki/themes/material-default.json');
const { remarkCodeHike } = require('@code-hike/mdx');
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
remarkPlugins: [[remarkCodeHike, { theme, showCopyButton: true }]],
},
});

这样我们就可以在mdx文件中添加代码块,code-hike会将代码渲染成带有高亮效果的组件。有关code-hike的使用参考 code-hike文档

博客内容页面设计

MDX 文件 Meta 数据导出

根据Nexts文档的建议,我们将mdx文件中的meta 数据定义方式按照如下格式修改:

pages/post/example.mdx
Copy

- ---
- title: '示例文章'
- date: '2023-03-22'
- ---
+ export const meta = {
+ title: '示例文章',
+ date: '2023-03-22',
}

这样我们可以直接将Meta数据导出到我们的自定义组件:


import Layout from "../../components/yourlayoutfile";
...
export default ({ children }) => <Layout meta={meta}>{children}</Layout>

这样我们就可以在自定义的Layout 中使用meta数据来展示文章信息,比如日期,作者,等等。


import { MDXProvider } from '@mdx-js/react';
const BlogContentLayout = ({ children, meta }) => {
return (
<Layout>
<Head>
<title>{meta.title}</title>
</Head>
<PostHeader
title={meta.title}
coverImage={meta.coverImage}
date={meta.date}
author={meta.authors[0]}
tags={meta.tags}
/>
<MDXContent>{children}</MDXContent>
</Layout>
);
};

主页设计

本站的主页以卡片的形式展示最新的几篇文章,因此需要批量获取最新文章的meta数据。这里需要实现NextJs的 getStaticProps方法:


export async function getStaticProps() {
const posts = await getAllPostPreviews();
return { props: { posts } };
}

以下是 getAllPostPreviews 函数的实现:

lib/api.js
Copy

async function importAll(r) {
const files = r.keys().filter((filename) => filename.startsWith('.'))
const modules = await Promise.all(files.map((filename) => r(filename)))
return files
.map((filename, index) => {
const { meta } = modules[index]
return {
slug: filename
.substr(2)
.replace(/\/index\.mdx$/, '')
.replace(/\.mdx$/, ''),
filename,
meta,
}
})
.filter(({ slug }) => !slug.includes('/snippets/'))
.sort((a, b) => dateSortDesc(a.meta.date, b.meta.date))
}
function dateSortDesc(a, b) {
if (a > b) return -1
if (a < b) return 1
return 0
}
export async function getAllPostPreviews() {
const files = require.context('../pages/posts/', true, /\.mdx$/)
const posts = await importAll(files)
return posts
}
export async function getAllPosts() {
const files = require.context('../pages/posts/', true, /\.mdx$/)
const posts = await importAll(files)
return posts.map((post) => {
const { content } = require(`../pages/posts/${post.slug}`)
return { ...post, content }
})
}