定制小程序开发费用【React】使用Next.js构建并部署个人博客

👉 TypeScript学习

👉 定制小程序开发费用蓝桥杯真题解析

👉 个人简介:定制小程序开发费用一个又菜又爱玩的前端小白🍬
👉 定制小程序开发费用你的一键三连是我更新定制小程序开发费用的最大动力❤️!

🏆定制小程序开发费用分享博主自用牛客网🏆:


前言

关于,定制小程序开发费用相信大家早已驾轻就熟,定制小程序开发费用网上有很多以markdown定制小程序开发费用驱动的博客框架,如,等,定制小程序开发费用这类框架的本质是生成静态站点,定制小程序开发费用而个人开发的博客系统定制小程序开发费用大多是使用数据库的全栈项目,定制小程序开发费用这两种方式各有各的好处,定制小程序开发费用这里就不做比较了

这篇文章我们将自己独立去开发并部署一个以markdown驱动的静态站点博客,所用技术栈如下:

  • React
  • TypeScript
  • Next.js
  • tailwindcss
  • Vercel部署

注意: 本文只是演示使用Next.js从0到1构建并部署一个个人博客项目,不会对项目构建过程中所用到的技术做详细的讲解,不过不用担心,只要跟着文章一步一步来,小白都能成功部署自己的个人博客!

项目仓库地址:
最终效果可见:

现在让我们开始吧!

开始之前请确保自己电脑上配置的有Node.js 12.13.0 或更高版本。

文章目录

1、创建Next.js项目

要创建 Next.js 应用程序,请打开终端,cd进入到要在其中创建应用程序的目录,然后运行以下命令:

npx create-next-app@latest --typescript ailjx-blog
  • 1

上述代码表示:通过create-next-app创建名为ailjx-blogTypeScript版本的Next.js应用程序

vscode打开ailjx-blog项目,目录结构如下:

在项目根目录终端运行以下命令启动项目

npm run dev
  • 1

打开显示如下页面:

2、安装tailwindcss

在项目根目录终端运行以下命令:

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
  • 1

生成tailwindcss配置文件:

npx tailwindcss init -p 
  • 1

此时项目里会多出两个文件:tailwind.config.jspostcss.config.js

修改tailwind.config.js文件里的content为:

    content: [        "./pages/**/*.{js,ts,jsx,tsx}",        "./components/**/*.{js,ts,jsx,tsx}",        "./styles/**/*.css",    ],
  • 1
  • 2
  • 3
  • 4
  • 5

pages文件夹下的_app.tsx文件的第一行添加:

import "tailwindcss/tailwind.css";
  • 1

之后重新启动项目

3、添加布局页面

准备一张自己的头像(建议比例为1:1,这里演示用的头像文件名为author.jpg

public文件夹下新建images文件夹,将你的头像图片放入其中,并删除public文件夹下的svg文件

public文件为项目的静态文件,可直接通过地址访问,如访问演示所用头像:http://localhost:3000/images/author.jpg

项目根目录下新建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>    );}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

这里使用了几个Next自带的组件:

  • Head:向Html页面的head内添加内容,里面内容自己根据需要修改
  • Image:渲染图像的组件,src地址修改为自己头像的地址
  • Link :页面间跳转组件

4、新建文章

项目根目录下新建posts文件夹,添加一个markdown文件,如:

欢迎来到我的博客.md

---title: "欢迎来到我的博客"date: "2022-08-08"---## 欢迎你!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意: 需要在每个markdown文件的顶部通过---添加元数据,元数据需要有title字段表示文章标题,date字段表示日期,如上面欢迎来到我的博客.md的元数据为:

---title: "欢迎来到我的博客"date: "2022-08-08"---
  • 1
  • 2
  • 3
  • 4

这些数据在我们渲染markdown内容时需要用到

5、解析markdown内容

需要安装以下插件:

  • remark-prism:代码高亮插件
  • date-fns:处理日期
  • gray-matter:获取元数据
  • next-mdx-remote:用于解析和渲染markdown内容
  • remark-external-links:对markdown内的链接添加reltarget,使其能够在新页面打开

在项目根目录终端运行以下命令安装上述插件:

 npm i remark-prism date-fns gray-matter next-mdx-remote remark-external-links
  • 1
npm i @types/remark-prism --D
  • 1

在项目根目录新建存放工具函数的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"]),    };}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

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>    );}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

修改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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

修改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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

删除style文件夹下的Home.module.css

此时运行项目,打开可见:

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";// 引入代码高亮cssimport "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,        },    };};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

之后在首页点击文章列表跳转到文章详情页面:

到此一个简单的博客项目就写好了

8、Vercel部署

没有Github账号的先去注册一个账号

在上新建一个名为next-blog的仓库(名称自己根据需要修改):

仓库权限公共私有都可,并且不需要使用README 或其他文件对其进行初始化

在我们的博客项目根目录下运行以下命令推送代码到Github仓库里:

git remote add origin https://github.com/<username>/next-blog.gitgit branch -M maingit push -u origin main
  • 1
  • 2
  • 3

请将上述第一行命令origin后面的地址替换成你的仓库地址,一般是将<username>替换为你Gitub的用户名,next-blog替换成你仓库的名称

之后刷新仓库查看代码:

项目仓库地址:

细心的大佬应该会发现我们这样提交代码是有问题的,因为我们并没有合并本地代码到本地仓库,所以提交到Github仓库的代码并不是我们最终的效果,而是创建Next.js时的初始效果。

不过不用担心,我们在后面会对其进行处理。当然,你也可以现在处理,直接将最新的代码同步到仓库,这样你就免了后面我们对其处理的操作

打开,没有Vercel账号的点击右上角的注册按钮进行注册,注册时选择通过Github注册,登录时也使用Github登录

登录Vecel成功后打开 或或点击新建项目按钮,之后进入到以下页面:


这个页面中会自动获取你的Github仓库,选择你刚刚推送博客项目的仓库,点击Import按钮,之后直接点击Deploy按钮:

稍等片刻,出现以下页面就部署成功了:

点击上述页面左侧的页面预览框就能跳转到你部署成功的网页了,但这时你会发现部署的页面不是我们最终的页面,而是创建Next.js时的初始页面,这是因为我们在Git提交代码到仓库时没有合并本地代码,我们重新提交一下就行了

我们可以在VScode里快速提交代码到仓库:


点击同步更改后会开始转圈,等待转圈结束就提交成功了,之后什么都不用干,仓库代码更新后Vercel会自动部署!!!

打开能查看到你已经部署的项目和对应的网页地址:

好啦,到此我们的任务就全部完成了,之后需要添加文章只需要在项目的posts文件内新建markdown文件就行了(不要忘记在markdown顶部添加元数据),更新完文章提交代码到仓库即可

结语

这次使用Next.js搭建个人博客只是一个小小的尝试,可以说是只搭建了一个骨架,其实走完整个流程你应该会有很多有趣的想法去完善填充你的博客,因为基础功能我们已经实现,剩下的就是锦上添花的操作了,这完全取决于你

项目仓库地址:
最终效果可见:

参考资料:

如果本篇文章对你有所帮助,还请客官一件四连!❤️

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发