使用Next.js - tailwind - shadcn/ui - velite搭建的Blog


1. 项目初始化

通过使用 Vercel 提供的 Next.js 模板快速创建项目, 项目创建完成后 Vercel 会自动部署项目, 并且会在GitHub仓库上创建项目,后面提交新代码会自动部署

2. 安装Velite

  • Velite 介绍

    Velite 是一个用于构建类型安全数据层的工具,它将 Markdown / MDX、YAML、JSON 或其他文件转换为具有 Zod 模式的应用程序数据层。

    Zod 是一个基于TypeScript的模式声明和验证库,它允许开发者声明数据模式,并自动推断出静态的TypeScript类型。使用Zod可以确保数据的完整性和一致性

  • Velite 配置文件
    Velite 安装完成后 在项目根目录添加 velite.config.ts 配置文件

import {defineConfig, defineCollection, s} from "velite"
import rehypeSlug from "rehype-slug"
import rehypePrettyCode from "rehype-pretty-code"
import rehypeAutolinkHeadings from "rehype-autolink-headings"
 
const computedFields = <T extends {slug: string}>(data : T) => ({
    ...data,
    slugAsParams: data.slug.split("/").slice(1).join("/")
})
 
const posts = defineCollection({
    name: "Post",
    pattern: "blog/**/*.mdx",
    schema: s.object({
        slug: s.path(),
        title: s.string().max(99),
        description: s.string().max(999).optional(),
        date: s.isodate(),
        published: s.boolean().default(true),
        body: s.mdx()
    })
    .transform(computedFields)
});
 
export default defineConfig({
    root: "content",
    output:{
        data:".velite",
        assets: "public/static",
        base: "/static/",
        name:"[name]-[hash:6].[ext]",
        clean: true,
    },
    collections: {posts},
    mdx:{
        rehypePlugins:[rehypeSlug, 
            [rehypePrettyCode, {theme: "houston"}],  
            [
                rehypeAutolinkHeadings,   
                {behavior:"wrap", properties:{className:["subheading-anchor"],   
                ariaLabel:"Link to section"}}
            ]
        ],
        remarkPlugins:[]
    }
})

defineCollection 函数定义了文件位置, 以及创建Zod 数据结构
rehypePlugins 定义插件 目前该项目使用了 rehype-slug 生成唯一标题, rehype-pretty-code 代码块样式,rehype-autolink-headings 将标题加入到链接
defineConfig 配置函数中的 output.data 是mdx文件转为数据结构文件输出位置 在 tsconfig.josn 配置路径别名

{
  "compilerOptions": {
    "paths": {
      "#site/content": ["./.velite"]
    }
  }
}

路径别名的在/app/xx/[xxx]/page.tsx中使用

import { posts } from '#site/content';
 
async function getPostFromParams(params: {params:Promise<{slug:string[]}>}) {
    const slug = (await params.params).slug.join("/")
    const psot = posts.find((psot) => psot.slugAsParams === slug)
    return psot;
}
  • next.js 配置文件
const isDev = process.argv.indexOf('dev') !== -1
const isBuild = process.argv.indexOf('build') !== -1
if (!process.env.VELITE_STARTED && (isDev || isBuild)) {
  process.env.VELITE_STARTED = '1'
  const { build } = await import('velite')
  await build({ watch: isDev, clean: !isDev })
}
 
/** @type {import('next').NextConfig} */
export default {
  // next config here...
}