react.createelement()(【React】使用Next.js构建并部署个人博客)
👉 TypeScript学习:TypeScript从入门到精通
👉 蓝桥杯真题解析:蓝桥杯Web国赛真题解析
👉 个人简介:一个又菜又爱玩的前端小白🍬
👉 你的一键三连是我更新的最大动力❤️!🏆分享博主自用牛客网🏆:一个非常全面的面试刷题求职网站 ,真的超级好用🍬
前言
关于博客系统 ,相信大家早已驾轻就熟 ,网上有很多以markdown驱动的博客框架 ,如vuepress ,hexo等 ,这类框架的本质是生成静态站点 ,而个人开发的博客系统大多是使用数据库的全栈项目 ,这两种方式各有各的好处 ,这里就不做比较了
这篇文章我们将自己独立去开发并部署一个以markdown驱动的静态站点博客,所用技术栈如下:
React TypeScript Next.js tailwindcss Vercel部署注意: 本文只是演示使用Next.js从0到1构建并部署一个个人博客项目 ,不会对项目构建过程中所用到的技术做详细的讲解 ,不过不用担心,只要跟着文章一步一步来 ,小白都能成功部署自己的个人博客!
项目仓库地址:https://github.com/Chen0807AiLJX/next-blog
最终效果可见:https://next-blog-eosin-six.vercel.app/现在让我们开始吧!
开始之前请确保自己电脑上配置的有Node.js 12.13.0 或更高版本 。
1 、创建Next.js项目
要创建 Next.js 应用程序 ,请打开终端,cd进入到要在其中创建应用程序的目录 ,然后运行以下命令:
npx create-next-app@latest --typescript ailjx-blog上述代码表示:通过create-next-app创建名为ailjx-blog的TypeScript版本的Next.js应用程序
用vscode打开ailjx-blog项目 ,目录结构如下:
在项目根目录终端运行以下命令启动项目:
npm run dev打开http://localhost:3000/显示如下页面:
2 、安装tailwindcss
在项目根目录终端运行以下命令:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest生成tailwindcss配置文件:
npx tailwindcss init -p此时项目里会多出两个文件:tailwind.config.js和postcss.config.js
修改tailwind.config.js文件里的content为:
content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./styles/**/*.css", ],在pages文件夹下的_app.tsx文件的第一行添加:
import "tailwindcss/tailwind.css";之后重新启动项目
3 、添加布局页面
准备一张自己的头像(建议比例为1:1 ,这里演示用的头像文件名为author.jpg)
在public文件夹下新建images文件夹 ,将你的头像图片放入其中 ,并删除public文件夹下的svg文件
public文件为项目的静态文件 ,可直接通过地址访问 ,如访问演示所用头像:
项目根目录下新建components文件夹 ,并添加布局文件layout.tsx:
import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; const name = "Ailjx"; // 名称 ,根据需要修改 export const siteTitle = "Ailjx Blog"; // 网站标题,根据需要修改 interface Props { children: React.ReactNode; home?: boolean; } export default function Layout({ children, home }: Props) { return ( <div className=max-w-2xl mx-auto px-4 mt-12 mb-24> <Head> <link rel=icon href=/favicon.ico /> <meta name=description content=AiljxBlog——Ailjx的博客 /> <meta property=og:image content={`https://og-image.vercel.app/${encodeURI( siteTitle )}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`} /> <meta name=og:title content={siteTitle} /> <meta name=twitter:card content=summary_large_image /> </Head> <header className=flex flex-col items-center> {home ? ( <> <Image priority src=/images/author.jpg className=rounded-full height={144} width={144} alt={name} /> <h1 className=text-5xl font-extrabold tracking-tighter my-4> {name} </h1> </> ) : ( <> <Link href=/> <a> <Image priority src=/images/author.jpg className=rounded-full height={108} width={108} alt={name} /> </a> </Link> <h2 className=text-2xl my-4> <Link href=/> <a>{name}</a> </Link> </h2> </> )} </header> <main>{children}</main> {!home && ( <div className=mt-12> <Link href=/> <a>← 返回首页</a> </Link> </div> )} </div> ); }这里使用了几个Next自带的组件:
Head:向Html页面的head内添加内容 ,里面内容自己根据需要修改 Image:渲染图像的组件 ,src地址修改为自己头像的地址 Link :页面间跳转组件4 、新建markdown文章
项目根目录下新建posts文件夹,添加一个markdown文件 ,如:
欢迎来到我的博客.md
--- title: "欢迎来到我的博客" date: "2022-08-08" --- ## 欢迎你!注意: 需要在每个markdown文件的顶部通过---添加元数据 ,元数据需要有title字段表示文章标题,date字段表示日期 ,如上面欢迎来到我的博客.md的元数据为:
--- title: "欢迎来到我的博客" date: "2022-08-08" ---这些数据在我们渲染markdown内容时需要用到
5 、解析markdown内容
需要安装以下插件:
remark-prism:代码高亮插件 date-fns:处理日期 gray-matter:获取元数据 next-mdx-remote:用于解析和渲染markdown内容 remark-external-links:对markdown内的链接添加rel和target ,使其能够在新页面打开在项目根目录终端运行以下命令安装上述插件:
npm i remark-prism date-fns gray-matter next-mdx-remote remark-external-links npm i @types/remark-prism --D在项目根目录新建存放工具函数的utils文件夹 ,里面新建处理markdown文件的posts.ts:
import fs from "fs"; import path from "path"; // gray-matter:获取元数据 import matter from "gray-matter"; // date-fns:处理日期 import { parseISO } from "date-fns"; import { serialize } from "next-mdx-remote/serialize"; // remark-prism:markdown代码高亮 import prism from "remark-prism"; // externalLinks:使markdown的链接是在新页面打开链接 import externalLinks from "remark-external-links"; interface MatterMark { data: { date: string; title: string }; content: string; [key: string]: unknown; } // posts目录的路径 const postsDirectory = path.join(process.cwd(), "posts"); // 获取posts目录下的所有文件名(带后缀) const fileNames = fs.readdirSync(postsDirectory); // 获取所有文章用于展示首页列表的数据 export function getSortedPostsData() { // 获取所有md文件用于展示首页列表的数据 ,包含id ,元数据(标题 ,时间) const allPostsData = fileNames.map((fileName) => { // 去除文件名的md后缀 ,使其作为文章id使用 const id = fileName.replace(/\.md$/, ""); // 获取md文件路径 const fullPath = path.join(postsDirectory, fileName); // 读取md文件内容 const fileContents = fs.readFileSync(fullPath, "utf8"); // 使用matter提取md文件元数据:{data:{//元数据},content:内容} const matterResult = matter(fileContents); return { id, ...(matterResult.data as MatterMark["data"]), }; }); // 按照日期从进到远排序 return allPostsData.sort(({ date: a }, { date: b }) => // parseISO:字符串转日期 parseISO(a) < parseISO(b) ? 1 : -1 ); } // 获取格式化后的所有文章id(文件名) export function getAllPostIds() { // 这是返回的格式: // [ // { // params: { // id: ...... // } // }, // { // params: { // id: ...... // } // } // ] return fileNames.map((fileName) => { return { params: { id: fileName.replace(/\.md$/, ""), }, }; }); } // 获取指定文章内容 export async function getPostData(id: string) { // 文章路径 const fullPath = path.join(postsDirectory, `${id}.md`); // 读取文章内容 const fileContents = fs.readFileSync(fullPath, "utf8"); // 使用matter解析markdown元数据和内容 const matterResult = matter(fileContents); return { content: await serialize(matterResult.content, { mdxOptions: { remarkPlugins: [prism, externalLinks] }, }), ...(matterResult.data as MatterMark["data"]), }; }posts.ts里有三个主要的函数:
getSortedPostsData:在首页用于展示文章列表
getAllPostIds:获取指定格式的所有文章id(文件名) ,这个格式是Next所要求的
因为我们在写文章详情页面时需要使用动态路由 ,每个文章的id就是一个路由,并且我们使用的Next静态站点生成会在项目打包构建时直接生成所有的html文件 ,需要把每一个路由对应的页面都构建出来 ,Next会根据getAllPostIds函数返回的这种格式的数据去构建每一个html页面
getPostData:获取文章详情,在文章详情页面会用到
6 、添加首页
首页会展示文章列表 ,会用到一个日期渲染组件 ,我们先创建一下
在components文件夹下新建date.tsx文件:
import { parseISO, format } from "date-fns"; interface Props { dateString: string; } export default function Date({ dateString }: Props) { const date = parseISO(dateString); return ( <time dateTime={dateString} className=text-gray-500> {format(date, "yyyy年MM月dd日")} </time> ); }修改pages文件夹下的index.tsx文件如下:
import type { NextPage, GetStaticProps } from "next"; import Head from "next/head"; import Layout, { siteTitle } from "../components/layout"; import Link from "next/link"; import Date from "../components/date"; import { getSortedPostsData } from "../utils/posts"; interface Props { allPostsData: { date: string; title: string; id: string; }[]; } const Home: NextPage<Props> = ({ allPostsData }) => { return ( <Layout home> <div> <Head> <title>{siteTitle}</title> </Head> <section className=text-xl leading-normal text-center> <p>你好,我是 Ailjx</p> <p>一个又菜又爱玩的前端小白 ,欢迎来到我的博客!</p> </section> <section className=text-xl leading-normal pt-4> <h2 className= text-2xl my-4 font-bold>Blog</h2> <ul> {allPostsData.map(({ id, date, title }) => ( <li key={id} className=mb-5> <Link href={`/posts/${id}`}> <a>{title}</a> </Link> <br /> <small> <Date dateString={date} /> </small> </li> ))} </ul> </section> </div> </Layout> ); }; export const getStaticProps: GetStaticProps = async () => { // 获取文章列表 const allPostsData = getSortedPostsData(); return { props: { allPostsData, }, }; }; export default Home;修改styles文件夹下的globals.css如下:
a { color: #0070f3; text-decoration: none; } a:hover { text-decoration: underline; } img { max-width: 100%; display: block; } ::-webkit-scrollbar { width: 5px; height: 5px; position: absolute; } ::-webkit-scrollbar-thumb { background-color: #0070f3; } ::-webkit-scrollbar-track { background-color: #ddd; }删除style文件夹下的Home.module.css
此时运行项目 ,打开http://localhost:3000/可见:
7 、添加文章详情页面
在pages文件夹下创建posts文件夹 ,在其中创建[id].tsx文件:
import type { GetStaticProps, GetStaticPaths } from "next"; import Layout from "../../components/layout"; import { getAllPostIds, getPostData } from "../../utils/posts"; import Head from "next/head"; import Date from "../../components/date"; import { MDXRemote, MDXRemoteProps } from "next-mdx-remote"; // 引入代码高亮css import "prismjs/themes/prism-okaidia.min.css"; interface Props { postData: { title: string; date: string; content: MDXRemoteProps; }; } export default function Post({ postData }: Props) { return ( <Layout> <Head> <title>{postData.title}</title> </Head> <h1 className=text-3xl font-extrabold my-4 tracking-tighter> {postData.title} </h1> <Date dateString={postData.date} /> <article className=py-8 prose prose-h1:mt-8> <MDXRemote {...postData.content} /> </article> </Layout> ); } // getStaticProps和getStaticPaths只在服务器端运行 ,永远不会在客户端运行 export const getStaticPaths: GetStaticPaths = async () => { // 获取所有文章id ,即所有路由 const paths = getAllPostIds(); return { paths, fallback: false, }; }; export const getStaticProps: GetStaticProps = async ({ params }) => { // 获取文章内容 const postData = await getPostData(params!.id as string); return { props: { postData, }, }; };之后在首页点击文章列表跳转到文章详情页面:
到此一个简单的博客项目就写好了8 、Vercel部署
没有Github账号的先去注册一个账号
在Github上新建一个名为next-blog的仓库(名称自己根据需要修改):
仓库权限公共私有都可 ,并且不需要使用README 或其他文件对其进行初始化
在我们的博客项目根目录下运行以下命令推送代码到Github仓库里:
git remote add origin https://github.com/<username>/next-blog.git git branch -M main git push -u origin main请将上述第一行命令origin后面的地址替换成你的仓库地址 ,一般是将<username>替换为你Gitub的用户名 ,next-blog替换成你仓库的名称
之后刷新仓库查看代码:
项目仓库地址:https://github.com/Chen0807AiLJX/next-blog
细心的大佬应该会发现我们这样提交代码是有问题的 ,因为我们并没有合并本地代码到本地仓库,所以提交到Github仓库的代码并不是我们最终的效果 ,而是创建Next.js时的初始效果 。
不过不用担心 ,我们在后面会对其进行处理 。当然,你也可以现在处理 ,直接将最新的代码同步到仓库 ,这样你就免了后面我们对其处理的操作
打开Vercel,没有Vercel账号的点击右上角的注册按钮进行注册 ,注册时选择通过Github注册 ,登录时也使用Github登录
登录Vecel成功后打开 https://vercel.com/import/git或https://vercel.com/new或点击新建项目按钮 ,之后进入到以下页面:
这个页面中会自动获取你的Github仓库 ,选择你刚刚推送博客项目的仓库 ,点击Import按钮 ,之后直接点击Deploy按钮:
稍等片刻 ,出现以下页面就部署成功了:
点击上述页面左侧的页面预览框就能跳转到你部署成功的网页了 ,但这时你会发现部署的页面不是我们最终的页面 ,而是创建Next.js时的初始页面,这是因为我们在Git提交代码到仓库时没有合并本地代码 ,我们重新提交一下就行了
我们可以在VScode里快速提交代码到仓库:
点击同步更改后会开始转圈 ,等待转圈结束就提交成功了,之后什么都不用干 ,仓库代码更新后Vercel会自动部署!!!
打开https://vercel.com/dashboard能查看到你已经部署的项目和对应的网页地址:
好啦 ,到此我们的任务就全部完成了,之后需要添加文章只需要在项目的posts文件内新建markdown文件就行了(不要忘记在markdown顶部添加元数据) ,更新完文章提交代码到仓库即可
结语
这次使用Next.js搭建个人博客只是一个小小的尝试 ,可以说是只搭建了一个骨架 ,其实走完整个流程你应该会有很多有趣的想法去完善填充你的博客 ,因为基础功能我们已经实现 ,剩下的就是锦上添花的操作了 ,这完全取决于你
项目仓库地址:https://github.com/Chen0807AiLJX/next-blog
最终效果可见:https://next-blog-eosin-six.vercel.app/参考资料:
Next.js官网 tailwindcss中文文档 date-fns文档 next-mdx-remote仓库 remark文档如果本篇文章对你有所帮助 ,还请客官一件四连!❤️
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!