使用Nextjs+Tailwind+Contentlayer搭建博客

本文章首发地址 使用 Nextjs+Tailwind+Contentlayer 搭建博客

Nextjs 是一个基于 React 的服务端渲染框架.
Tailwind 无需书写 CSS,即可快速构建美观的网站的组件库.
Contentlayer 将内容转换成 JSON 并导入应用程序.

前期准备

初始化项目

bash 复制代码
# 使用命令行
npx create-next-app@latest

Nextjs应用页面层次如下

  • layout.js
  • template.js
  • error.js (React error boundary)
  • loading.js (React suspense boundary)
  • not-found.js (React error boundary)
  • page.js

对应React渲染路由如下

嵌套路由只需要嵌套父应用里面即可

新建博客页面

ts 复制代码
type BlogSlugProps = {
  params: {
    slug: string
  }
}

export default function BlogSlug({ params }: BlogSlugProps) {
  return (
    <section>
      {params.slug}
    </section>
  )
}

新建 MDX 文件

md 复制代码
---
title: 'template'
publishedAt: '2023-11-11'
summary: 'This is your first blog post.'
---

first blog

解析 MDX

使用Contentlayer来解析 mdx 文件,新建contentlayer.config.ts文件

安装以下依赖

sh 复制代码
pnpm add contentlayer next-contentlayer @tailwindcss/typography reading-time remark-gfm rehype-slug rehype-autolink-headings rehype-pretty-code
ts 复制代码
import { defineDocumentType, makeSource } from 'contentlayer/source-files'
import readingTime from 'reading-time'
import remarkGfm from 'remark-gfm'
import rehypeSlug from 'rehype-slug'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypePrettyCode from 'rehype-pretty-code'

export const Blog = defineDocumentType(() => ({
  name: 'Blog',
  filePathPattern: '**/*.mdx',
  contentType: 'mdx',
  // 定义入口字段
  fields: {
    title: {
      type: 'string',
      required: true,
    },
    summary: {
      type: 'string',
      required: true,
    },
    publishedAt: {
      type: 'string',
      required: true,
    },
  },
  // 定义额外出参
  computedFields: {
    slug: {
      type: 'string',
      resolve: (doc) => doc._raw.flattenedPath,
    },
    readingTime: {
      type: 'nested',
      resolve: (doc) => readingTime(doc.body.code),
    },
  },
}))

export default makeSource({
  contentDirPath: 'content',
  documentTypes: [Blog],
  mdx: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [
      rehypeSlug,
      [
        rehypePrettyCode,
        {
          // 代码主题类型 https://unpkg.com/browse/shiki@0.14.2/themes/
          theme: 'one-dark-pro',
          // To apply a custom background instead of inheriting the background from the theme
          keepBackground: false,
        },
      ],
      [
        rehypeAutolinkHeadings,
        {
          properties: {
            // 锚点类名
            className: ['anchor'],
          },
        },
      ],
    ],
  },
})

修改next.config.js文件

js 复制代码
const { withContentlayer } = require('next-contentlayer')

/** @type {import('next').NextConfig} */
const nextConfig = {}

module.exports = withContentlayer(nextConfig)

运行 pnpm dev 会发现项目中新增一个 .contentlayer 文件夹,打开后可以找到我们编写的 .mdx 文件已经被解析成对应的 .json 文件。由于 .contentlayer 该文件夹是运行的时候生成的,我们需要在提交代码的时候忽略掉,需要在 .gitignore 文件中增加 .contentlayer

tailwind.config.ts 文件需要配置 @tailwindcss/typography 插件

ts 复制代码
import type { Config } from 'tailwindcss'
import typography from '@tailwindcss/typography'

const config: Config = {
  darkMode: 'class',
  content: [
    './app/**/*.{ts,tsx}',
    './components/**/*.{ts,tsx}',
    './content/**/*.mdx',
  ],
  theme: {
    extend: {},
  },
  plugins: [typography],
}
export default config

解析数据

我们打开 app/page.tsx 文件,修改代码如下

ts 复制代码
import { allBlogs } from 'contentlayer/generated'
import Link from 'next/link'

export default function Home() {
  return (
    <section>
      {allBlogs
        .sort((a, b) => {
          if (new Date(a.publishedAt) > new Date(b.publishedAt)) {
            return -1
          }
          return 1
        })
        .map((item) => (
          <Link
            key={item.slug}
            href={`/blog/${item.sulg}`}
            className='mb-5'
          >
            {item.title}
          </Link>
        ))}
    </section>
  )
}

页面展示如下

修改 app/blog/[slug]/page.tsx 文件

ts 复制代码
import { allBlogs } from 'contentlayer/generated'
import { notFound } from 'next/navigation'
import { useMDXComponent } from 'next-contentlayer/hooks'

type BlogSlugProps = {
  params: {
    slug: string
  }
}

export default function BlogSlug({ params }: BlogSlugProps) {
  const post = allBlogs.find((post) => post.slug === params.slug)
  if (!post) {
    notFound()
  }

  const Component = useMDXComponent(post.body.code)

  return (
    <section className="prose prose-stone">
      <Component />
    </section>
  )
}

页面展示如下

目前为止,整个博客结构体系搭建完成,可以愉快的编写 MDX 文件来写博客了

相关推荐
崔庆才丨静觅19 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606119 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了19 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅19 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅20 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅20 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment20 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅21 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊21 小时前
jwt介绍
前端
爱敲代码的小鱼21 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax