使用Next。js创建Blog
Next.js 已经成为 React 应用程序最重要的框架之一。它可以帮助开发人员在没有模板的情况下构建更好的服务器端渲染 React 应用程序。
Next.js 之所以能成为目前最好的 React 框架之一,与其很多特性离不开,比如打包构建、路由预取、TypeScript、SEO 等。
对于那些想要拥有一个简单但功能强大的博客的人来说,使用 Next.js 创建博客是当今的最佳选择。
SEO(搜索引擎优化)是改进应用程序在搜索引擎排名的过程。对于任何想要在搜索引擎上获得更好排名并带来更多流量的博客来说,这都是非常重要的。
我们将在本文中使用 Next.js 来构建博客。我们将介绍 SSG(静态站点生成)的工作原理,并完成 SEO 友好的博客。 入门
使用官方推荐的Create Next App创建项目 npx create-next-app@latest --typescript # or yarn create next-app --typescript # or pnpm create next-app --typescript 复制代码
为什么要使用Create Next App 交互式体验:不带任何参数运行npx create-next-app@latest,将会开启交互模式,引导创建项目 零依赖:Create Next App没有依赖,毫秒级创建项目 离线支持:Create Next App侦测网络状态,无网状态将使用本地依赖缓存 支持模板:通过加入--example参数,可以拉取官方仓库任何模板 集成测试:集成测试功能
创建完成后项目目录构造如下: . README.md next-env.d.ts next.config.js node_modules package.json pages pnpm-lock.yaml public styles tsconfig.json 复制代码安装依赖pnpm install globby gray-matter dayjs @chakra-ui/react prismjs @emotion/react @emotion/styled framer-motion next-mdx-remote remark-gfm 复制代码创建文章
根目录新增_posts目录,在_posts目录下创建两个mdx文件(_posts/js/helloWorld.mdx,_posts/demo.mdx),为什么是mdx文件呢?mdx支持渲染组件,支持引入导出组件,详细文档参考MDX 创建公共函数目录
根目录新增utils目录,在utils目录下创建getAllPosts.js并写入如下函数 import fs from "fs" import {globby} from "globby" import matter from "gray-matter" const dayjs = require("dayjs") const relativeTime = require("dayjs/plugin/relativeTime") dayjs.extend(relativeTime) //获取所有文章 const GetAllPosts = async () => { const posts = await globby(["_posts"]) return posts .reduce((prev, next) => { const fileContents = fs.readFileSync(next, "utf8") const {data, content} = matter(fileContents) const postData = { ...data, group: dayjs(data.date).format("MMM/YYYY"), date: dayjs(data.date).format("MMM DD, YYYY"), fromNow: dayjs(data.date).fromNow(), modified: dayjs(data.modified).format("MMM DD, YYYY"), content, slug: next.replace(/^_posts//, "").replace(/.mdx$/, "") } !data.draft && prev.push(postData) return prev }, []) .sort((a, b) => dayjs(b.date) - dayjs(a.date)) } // 根据slug导出文章 const GetPostBySlug = (slug) => { // eslint-disable-next-line no-undef return new Promise((resolve, reject) => { GetAllPosts() .then((posts) => { const post = posts.find((post) => post.slug.includes(`${slug.join("/")}`) ) resolve(post) }) .catch(() => { reject({}) }) }) } export {GetAllPosts, GetPostBySlug} 复制代码创建组件
根目录新增components目录 创建PostPage.tsx组件,内容如下: import React, {useEffect} from "react" import Prism from "prismjs" import {Box} from "@chakra-ui/react" // 以下按需引入 require("prismjs/components/prism-go") require("prismjs/components/prism-python") require("prismjs/components/prism-javascript") require("prismjs/components/prism-css") require("prismjs/components/prism-bash") require("prismjs/components/prism-swift") require("prismjs/components/prism-tsx") require("prismjs/components/prism-jsx") require("prismjs/components/prism-typescript") require("prismjs/components/prism-sql") require("prismjs/themes/prism-okaidia.min.css") const PostPage = ({children}) => { useEffect(() => { const highlight = async () => { await Prism.highlightAll() } highlight().then(() => {}) }, [children]) return ( {children} ) } export default PostPage 复制代码创建pages/index.tsx import NextLink from "next/link" import {Fragment} from "react" import { List, LinkOverlay, ListItem, Container, Heading, Image } from "@chakra-ui/react" const IndexPage = ({groupByMonthPosts}) => { return ( {Object.keys(groupByMonthPosts).map((group) => { return ( {group} {groupByMonthPosts[group].map((post) => { return ( {post.title} {post.tags.map((tag) => { return ( ) })} ) })}
) })} ) } export default IndexPage export async function getStaticProps() { const {GetAllPosts} = await import("utils/getAllPosts") const posts = await GetAllPosts() const groupByMonthPosts = posts.reduce((prev, next) => { if (Array.isArray(prev[next.group])) { prev[next.group].push(next) } else { prev[next.group] = [] prev[next.group].push(next) } return prev }, {}) return { props: { groupByMonthPosts } } } 复制代码创建pages/[...slug].tsx import {MDXRemote} from "next-mdx-remote" import {serialize} from "next-mdx-remote/serialize" import dynamic from "next/dynamic" import ErrorPage from "next/error" import NextLink from "next/link" import {useRouter} from "next/router" import React from "react" import remarkGfm from "remark-gfm" import components from "utils/components" import { Container, Box, Heading, Text, Link, Image, Center } from "@chakra-ui/react" const PostPage = dynamic(() => import("components/PostPage")) const Post = ({title, description, date, originalUrl, mdxSource, cover}) => { const router = useRouter() if (!router.isFallback && !mdxSource) { return } return ( Published {date} {title} {originalUrl && ( 本文翻译自: {originalUrl} )} ) } export const getStaticPaths = async () => { const {GetAllPosts} = await import("utils/getAllPosts") const allPosts = await GetAllPosts() const paths = allPosts.map((post) => ({ params: { slug: post.slug.split("/") } })) return { paths, fallback: false } } export const getStaticProps = async ({params}) => { const {GetPostBySlug} = await import("utils/getAllPosts") const {content, ...data} = await GetPostBySlug(params.slug) const mdxSource = await serialize(content, { mdxOptions: { remarkPlugins: [[remarkGfm]], rehypePlugins: [] }, scope: data }) return { props: { ...data, mdxSource } } } export default Post 复制代码
至此,基本框架搭建完成,接下来调整样式及组件的引入,以及 mdx 渲染修正。 调整样式
可选
引入tailwind.css,执行pnpm install -D tailwindcss postcss autoprefixer && npx tailwindcss init -p
修改tailwind.config.js,如下: /** @type {import("tailwindcss").Config} */ module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}" ], theme: { extend: {} }, plugins: [] } 复制代码
修改全局样式styles/globals.scss @tailwind base; @tailwind components; @tailwind utilities; 复制代码
必须
修改pages/_app.tsx,引入chakra-ui的配置 // pages/_app.js import {ChakraProvider} from "@chakra-ui/react" function MyApp({Component, pageProps}) { return ( ) } export default MyApp 复制代码
到这里,不出意外,你的界面应该是长这样
点击链接,应该会报错,未引入utils/components,这个是配置 mdx 内元素渲染的组件,参考MDX Components,mdx 提供默认的渲染组件,所以,这个是非必须的,不需要删除即可
个人比较喜欢 chakra-ui,所以将组件都转成了 chakra-ui 提供的组件,配置如下: import CanIUse from "components/CanIUse" import {Heading, Link, Box} from "@chakra-ui/react" import {FiExternalLink} from "react-icons/fi" const components = { CanIUse, h2: (props) => ( {props.children} ), h3: (props) => ( {props.children} ), h4: (props) => ( {props.children} ), h5: (props) => ( {props.children} ), p: (props) => ( {props.children} ), p: (props) => {props.children}, a: (props) => { return ( {props.children} ) } } export default components 复制代码
好了,到这里基本完成了基于Next.js的博客搭建。 部署到Vercel
Next.js部署到Vercel无需更改和配置,无缝衔接。
【Source Code】
也可以参考我的个人网站Manon.icu | Home