使用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

创建完成后项目目录构造如下:

.
  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')
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
复制代码
  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 (
    
      {Object.keys(groupByMonthPosts).map((group) => {
        return (
          
            
              {group}
            
            
              {groupByMonthPosts[group].map((post) => {
                return (
                  
                    
                      {post.title}
                    
                    {post.tags.map((tag) => {
                      return (
                        {tag}
                      )
                    })}
                  
                )
              })}
            
          
        )
      })}
    
  )
}

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 
  }
  return (
    
      
      
        
          Published {date}
        
        
          {title}
        
        {originalUrl && (
          
本文翻译自: {originalUrl}
)}
{title}
) } 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 (
    
      
    
  )
}

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

展开阅读全文

页面更新:2024-04-15

标签:根目录   应用程序   样式   组件   框架   模板   代码   目录   项目   博客

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top