您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

使用Next.js创建Blog

时间:2022-11-25 11:42:15  来源:  作者:Manon

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目录

  1. 创建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/ target=_blank class=infotextkey>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 (
    <Box position="relative" w="2/3" fontSize="text.sm">
      {children}
    </Box>
  )
}
export default PostPage
复制代码
  1. 创建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 (
    <Container>
      {Object.keys(groupByMonthPosts).map((group) => {
        return (
          <Fragment key={group}>
            <Heading as="h3" mt={12} mb={4}>
              {group}
            </Heading>
            <List spacing={3}>
              {groupByMonthPosts[group].map((post) => {
                return (
                  <ListItem
                    position="relative"
                    display="flex"
                    gap={2}
                    alignItems="center"
                    key={post.title}
                  >
                    <NextLink
                      legacyBehavior
                      href={`/${post.slug}`}
                      passHref
                    >
                      <LinkOverlay>{post.title}</LinkOverlay>
                    </NextLink>
                    {post.tags.map((tag) => {
                      return (
                        <Image
                          key={tag}
                          boxSize={4}
                          objectFit="cover"
                          alt={tag}
                          src={`https://pics-Rust.vercel.app/uPic/icons/${tag}.svg`}
                        />
                      )
                    })}
                  </ListItem>
                )
              })}
            </List>
          </Fragment>
        )
      })}
    </Container>
  )
}

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
    }
  }
}
复制代码
  1. 创建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 <ErrorPage statusCode={404} />
  }
  return (
    <Container
      mt={20}
      maxW={{
        sm: 'container.sm',
        md: 'container.md',
        lg: 'container.2xl',
        xl: 'container.xl'
      }}
      className="post"
    >
      <NextSeo
        title={title}
        description={description}
        openGraph={{title, description}}
      />
      <Box as="hgroup">
        <Text textAlign="center" color="gray.500" fontSize="xs" as="p">
          Published {date}
        </Text>
        <Heading textAlign="center" as="h1" mt={4} mb={2}>
          {title}
        </Heading>
        {originalUrl && (
          <Center color="gray.500" fontSize="sm" mb={8}>
            本文翻译自:
            <NextLink legacyBehavior href={originalUrl} passHref>
              <Link>{originalUrl}</Link>
            </NextLink>
          </Center>
        )}
      </Box>
      <Image
        boxSize="100%"
        src={
          cover ??
          'https://cdn.jsdelivr.NET/gh/manonicu/pics@master/uPic/NhSU3O.jpg'
        }
        alt={title}
      />

      <PostPage>
        <MDXRemote {...mdxSource} components={components} />
      </PostPage>
    </Container>
  )
}

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 渲染修正。

  1. 调整样式

可选

引入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 (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}

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) => (
    <Heading as="h2" mb={4}>
      {props.children}
    </Heading>
  ),
  h3: (props) => (
    <Heading as="h3" mb={4}>
      {props.children}
    </Heading>
  ),
  h4: (props) => (
    <Heading as="h4" mb={4}>
      {props.children}
    </Heading>
  ),
  h5: (props) => (
    <Heading as="h5" mb={4}>
      {props.children}
    </Heading>
  ),
  p: (props) => (
    <Box as="div" mb={4}>
      {props.children}
    </Box>
  ),
  div: (props) => <Box mb={4}>{props.children}</Box>,
  a: (props) => {
    return (
      <Link
        display="inline-flex"
        alignItems="center"
        href={props.href}
        gap={2}
        isExternal
      >
        {props.children}
        <FiExternalLink />
      </Link>
    )
  }
}

export default components
复制代码

好了,到这里基本完成了基于Next.js的博客搭建。

部署到Vercel

Next.js部署到Vercel无需更改和配置,无缝衔接。

 


 


 


 

【Source Code】

也可以参考我的个人网站Manon.icu | Home



Tags:Next.js   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
怎么理解 React Server Component 和 Next.js 的关系
最近Next.js v14发布,发布会的各种梗图刷爆了国外前端社区。Next.js的诸多特性(比如Server Action、App Router),都是在RSC(React Server Component)基础上衍生出的。从名字可以看...【详细内容】
2023-11-16  Search: Next.js  点击:(210)  评论:(0)  加入收藏
原来真的可以在 Next.js 中写 PHP 代码?
Next.js 14 近期发布,其中一个重大的功能点是 Server Actions 成为稳定版,因其超前和熟悉的开发方式,在社交网络上引起了一阵讨论,应该是下面这张图的内容了。图片有人说这是又...【详细内容】
2023-11-07  Search: Next.js  点击:(297)  评论:(0)  加入收藏
Next.js的崛起:为什么它是现代网站的首选全栈框架
在选择前端框架时,可靠性对我的客户至关重要。尽管我研究了诸如SvelteKit等选项,但"为什么选择Next.js?"仍然是一个经常被问到的问题。在这篇文章中,我将详细解释为什么Next.js...【详细内容】
2023-11-02  Search: Next.js  点击:(285)  评论:(0)  加入收藏
Next.js支持在前端代码中写SQL,开倒车还是遥遥领先?
出品 | OSC开源社区(ID:oschina2013)下面这张图来自近日举办的 Next.js Conf 2023,里面的代码使用了名为「Server Actions」的特性 &mdash;&mdash;在前端代码中使用 SQL 语句直...【详细内容】
2023-10-29  Search: Next.js  点击:(293)  评论:(0)  加入收藏
Next.js 13.5 正式发布,速度大幅提升!
9 月 19 日,Next.js 13.5 正式发布,该版本通过以下方式提高了本地开发性能和可靠性: 本地服务器启动速度提高 22%:使用App和Pages Router可以更快地进行迭代 HMR(快速刷新)速度...【详细内容】
2023-09-20  Search: Next.js  点击:(149)  评论:(0)  加入收藏
使用Next.js创建Blog  给Blog添加主题模式和seo
前置【上一篇】完成了使用 Next.js 构建 blog。注意主题切换基于chakra-ui、tailwindcss(可选),chakra-ui 和 tailwind.css 都内置支持颜色模式chakra-ui 将颜色模式变量值...【详细内容】
2022-11-25  Search: Next.js  点击:(433)  评论:(0)  加入收藏
使用Next.js创建Blog
Next.js 已经成为 React 应用程序最重要的框架之一。它可以帮助开发人员在没有模板的情况下构建更好的服务器端渲染 React 应用程序。Next.js 之所以能成为目前最好的 React...【详细内容】
2022-11-25  Search: Next.js  点击:(519)  评论:(0)  加入收藏
如何使用 Next.js、 Prisma、Postgres 和 Fastify 构建全栈应用程序
在本文中,我们将学习如何使用 Next.js、 Prisma、 Postgres 和 Fastify 构建一个 Full-stack 应用程序。在本文中,我们将学习如何使用 Next.js、 Prisma、 Postgres 和 Fastif...【详细内容】
2022-07-12  Search: Next.js  点击:(715)  评论:(0)  加入收藏
记一次使用next.js开发官网经历
1.前情提要 最近我司官网重构,一个官网配一个后台,目的是动态配置官网内容,以前都是数据库写死的,为了更好的运营网站,体现品牌影响力,于是有了这一次大变革。2.需求分析需求不复...【详细内容】
2020-09-30  Search: Next.js  点击:(934)  评论:(0)  加入收藏
如何优雅的部署一个 Serverless Next.js 应用
本篇专门针对 Next.js 的 SSR 方案进行了探索和优化,一步一步带大家了解,如何基于 Serverless 架构部署一个实际的线上业务。 本文主要内容: 如何快速部署 Serverless Next.js ...【详细内容】
2020-07-24  Search: Next.js  点击:(381)  评论:(0)  加入收藏
▌简易百科推荐
Qt与Flutter:在跨平台UI框架中哪个更受欢迎?
在跨平台UI框架领域,Qt和Flutter是两个备受瞩目的选择。它们各自具有独特的优势,也各自有着广泛的应用场景。本文将对Qt和Flutter进行详细的比较,以探讨在跨平台UI框架中哪个更...【详细内容】
2024-04-12  刘长伟    Tags:UI框架   点击:(1)  评论:(0)  加入收藏
Web Components实践:如何搭建一个框架无关的AI组件库
一、让人又爱又恨的Web ComponentsWeb Components是一种用于构建可重用的Web元素的技术。它允许开发者创建自定义的HTML元素,这些元素可以在不同的Web应用程序中重复使用,并且...【详细内容】
2024-04-03  京东云开发者    Tags:Web Components   点击:(8)  评论:(0)  加入收藏
Kubernetes 集群 CPU 使用率只有 13% :这下大家该知道如何省钱了
作者 | THE STACK译者 | 刘雅梦策划 | Tina根据 CAST AI 对 4000 个 Kubernetes 集群的分析,Kubernetes 集群通常只使用 13% 的 CPU 和平均 20% 的内存,这表明存在严重的过度...【详细内容】
2024-03-08  InfoQ    Tags:Kubernetes   点击:(19)  评论:(0)  加入收藏
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  风舞凋零叶    Tags:Spring Security   点击:(55)  评论:(0)  加入收藏
五大跨平台桌面应用开发框架:Electron、Tauri、Flutter等
一、什么是跨平台桌面应用开发框架跨平台桌面应用开发框架是一种工具或框架,它允许开发者使用一种统一的代码库或语言来创建能够在多个操作系统上运行的桌面应用程序。传统上...【详细内容】
2024-02-26  贝格前端工场    Tags:框架   点击:(47)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  程序员wayn  微信公众号  Tags:Spring   点击:(39)  评论:(0)  加入收藏
开发者的Kubernetes懒人指南
你可以将本文作为开发者快速了解 Kubernetes 的指南。从基础知识到更高级的主题,如 Helm Chart,以及所有这些如何影响你作为开发者。译自Kubernetes for Lazy Developers。作...【详细内容】
2024-02-01  云云众生s  微信公众号  Tags:Kubernetes   点击:(51)  评论:(0)  加入收藏
链世界:一种简单而有效的人类行为Agent模型强化学习框架
强化学习是一种机器学习的方法,它通过让智能体(Agent)与环境交互,从而学习如何选择最优的行动来最大化累积的奖励。强化学习在许多领域都有广泛的应用,例如游戏、机器人、自动驾...【详细内容】
2024-01-30  大噬元兽  微信公众号  Tags:框架   点击:(68)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  HELLO程序员  微信公众号  Tags:Spring   点击:(88)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19   Java中文社群  微信公众号  Tags:SpringBoot   点击:(86)  评论:(0)  加入收藏
站内最新
站内热门
站内头条