使用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/[email protected]/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 文件来写博客了

相关推荐
拉不动的猪16 分钟前
前端常见数组分析
前端·javascript·面试
小吕学编程33 分钟前
ES练习册
java·前端·elasticsearch
Asthenia041240 分钟前
Netty编解码器详解与实战
前端
袁煦丞1 小时前
每天省2小时!这个网盘神器让我告别云存储混乱(附内网穿透神操作)
前端·程序员·远程工作
一个专注写代码的程序媛2 小时前
vue组件间通信
前端·javascript·vue.js
一笑code2 小时前
美团社招一面
前端·javascript·vue.js
懒懒是个程序员2 小时前
layui时间范围
前端·javascript·layui
NoneCoder2 小时前
HTML响应式网页设计与跨平台适配
前端·html
凯哥19702 小时前
在 Uni-app 做的后台中使用 Howler.js 实现强大的音频播放功能
前端
烛阴3 小时前
面试必考!一招教你区分JavaScript静态函数和普通函数,快收藏!
前端·javascript