优化 Nextjs 开发的个人博客首页,秒开!

我有一个使用 Nextjs 开发的个人博客,功能大概有首页、文章列表、留言板、创建编辑文章

但是我对 Nextjs 是干中学的状态,属于是一边学一边写

使用 vercel 部署后总觉得这个博客首页打开有点慢 🧐 猛击查看我的个人博客

首页会获取 GitHub、掘金的个人信息进行展示,掘金还好,GitHub 获取用户信息的接口是有点慢的

所以我做了以下处理:1. 骨架屏 2. 接口缓存

但还是慢因为需要先加载骨架屏然后在渲染出实际内容,虽然能很快加载出页面,但是看到页面实际内容会慢

当时觉得可以优化,但是不知道怎么去优化,写完就没再动了

最近看了点 Nextjs 的文档和文章,有了点思路然后就开始审查首页的代码

在查看代码的时候发现使用了 'use client' 声明,也就是渲染 掘金、GitHub 信息的时候使用了客户端渲染

以掘金文章模块为例

ts 复制代码
'use client'

import { Icon } from '@iconify/react'
import { ContentCard } from './ContentCard'
import { useEffect, useState } from 'react'
import { Skeleton } from '@/components/ui/skeleton'
import Link from 'next/link'
import { Article } from '@prisma/client'
import { TimeInSeconds } from '@/lib/enums'

function NoFound() {
  return <p className="text-center text-gray-500 dark:text-gray-400 py-8">No articles found.</p>
}

function LoadingComponent() {
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
      {[...Array(6)].map((_, index) => (
        <div key={index}>
          <Skeleton className="h-6 w-3/4 mb-2" />
          <Skeleton className="h-4 w-full mb-2" />
          <Skeleton className="h-4 w-1/4" />
          {index < 2 && <div className="my-6 border-b border-gray-200 dark:border-gray-700"></div>}
        </div>
      ))}
    </div>
  )
}

function ArticleInfo({ articles }: { articles: Article[] }) {
  return (
    <div key="content" className="grid grid-cols-1 md:grid-cols-2 gap-6">
      {articles?.map((article) => (
        <div
          key={article.id}
          className="block transition duration-300 ease-in-out hover:bg-black/10 dark:hover:bg-white/10 rounded p-2"
        >
          <Link
            href={`https://juejin.cn/post/${article.id}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <h3 className="text-lg font-semibold mb-2 transition duration-300">{article.title}</h3>
            <p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
              {article.summary || 'No description available'}
            </p>
            <div className="flex items-center text-sm text-gray-500 dark:text-gray-400 space-x-4">
              <span className="flex items-center">
                <Icon icon="mdi:star" className="w-4 h-4 mr-1 text-yellow-500" />
                {article.favorites}
              </span>
              <span className="flex items-center">
                <Icon icon="mdi:thumb-up" className="w-4 h-4 mr-1 text-green-500" />
                {article.likes}
              </span>
              <span className="flex items-center">
                <Icon icon="mdi:eye" className="w-4 h-4 mr-1 text-blue-500" />
                {article.views}
              </span>
            </div>
          </Link>
        </div>
      ))}
    </div>
  )
}

export function JueJinArticles() {
  const [articles, setArticles] = useState<Article[]>([])
  const [isLoading, setIsLoading] = useState(true)

  useEffect(() => {
    const loadData = async () => {
      try {
        const res = await fetch('/api/articles/all', {
          next: { revalidate: TimeInSeconds.oneHour }
        }).then((res) => res.json())

        if (res.code === 0) {
          const list: Article[] = res?.data || []
          const sortedData = list.sort((a, b) => b.likes - a.likes).slice(0, 6)
          setArticles(sortedData)
        }
      } catch (error) {
        console.error('Failed to fetch articles:', error)
      } finally {
        setIsLoading(false)
      }
    }
    loadData()
  }, [])

  return (
    <ContentCard title="掘金文章">
      {isLoading ? <LoadingComponent /> : <ArticleInfo articles={articles} />}

      {articles.length === 0 && !isLoading && <NoFound />}
    </ContentCard>
  )
}

看代码其实也能发现是因为使用了 useEffect、useState 所以只能使用客户端渲染

首页中需要获取数据的组件基本都是这个逻辑

在看文档时候发现可以这样写,使用服务端渲染

代码改造后如下:

ts 复制代码
import { Icon } from '@iconify/react'
import { ContentCard } from './ContentCard'
import Link from 'next/link'
import { Article } from '@prisma/client'
import { TimeInSeconds } from '@/lib/enums'

function NoFound() {
  return <p className="text-center text-gray-500 dark:text-gray-400 py-8">No articles found.</p>
}

function ArticleList({ articles }: { articles: Article[] }) {
  return (
    <div key="content" className="grid grid-cols-1 md:grid-cols-2 gap-6">
      {articles?.map((article) => (
        <div
          key={article.id}
          className="block transition duration-300 ease-in-out hover:bg-black/10 dark:hover:bg-white/10 rounded p-2"
        >
          <Link
            href={`https://juejin.cn/post/${article.id}`}
            target="_blank"
            rel="noopener noreferrer"
          >
            <h3 className="text-lg font-semibold mb-2 transition duration-300">{article.title}</h3>
            <p className="text-sm text-gray-600 dark:text-gray-400 mb-3 line-clamp-2">
              {article.summary || 'No description available'}
            </p>
            <div className="flex items-center text-sm text-gray-500 dark:text-gray-400 space-x-4">
              <span className="flex items-center">
                <Icon icon="mdi:star" className="w-4 h-4 mr-1 text-yellow-500" />
                {article.favorites}
              </span>
              <span className="flex items-center">
                <Icon icon="mdi:thumb-up" className="w-4 h-4 mr-1 text-green-500" />
                {article.likes}
              </span>
              <span className="flex items-center">
                <Icon icon="mdi:eye" className="w-4 h-4 mr-1 text-blue-500" />
                {article.views}
              </span>
            </div>
          </Link>
        </div>
      ))}
    </div>
  )
}

async function getJueJinArticles() {
  try {
    const res = await fetch(`${process.env.NEXT_PUBLIC_SITE_URL}/api/articles/all`, {
      next: { revalidate: TimeInSeconds.oneHour }
    })
    const json = await res.json()
    return json.code === 0 ? json.data : []
  } catch {
    return []
  }
}

export async function JueJinArticles() {
  const list = (await getJueJinArticles()) as Article[]

  const articles = list.sort((a, b) => b.likes - a.likes).slice(0, 6)

  return (
    <ContentCard title="掘金文章">
      {articles.length === 0 ? <NoFound /> : <ArticleList articles={articles} />}
    </ContentCard>
  )
}

改为服务端渲染,调整后去除了骨架屏的逻辑、修改了数据获取的逻辑、减少了几十行代码

测试可以正常运行后就把其他的组件依葫芦画瓢做了修改,最终成果如下

优化前后对比

优化前-禁用缓存(这个因为无法在无痕打开所以就没开无痕)

优化后-无痕模式、禁用缓存

关于数据缓存

ts 复制代码
const res = await fetch('/api/articles/all', {
          next: { revalidate: TimeInSeconds.oneHour }
        }).then((res) => res.json())

这里 revalidate 是指数据重新验证的时间,比如这里是一小时,表示最少需要一小时数据才会更新,但并不是每一个小时更新一次,假设你在一小时后第一次访问这个接口返回的依然是缓存的数据、同时更新数据,第二次访问的时候就是新数据了

总结

类似个人博客等对内容新鲜度没有太高要求的页面可以使用 服务端渲染 + 数据缓存的方式,加快页面的访问速度

相关推荐
abigale0330 分钟前
webpack+vite前端构建工具 -11实战中的配置技巧
前端·webpack·node.js
专注API从业者1 小时前
构建淘宝评论监控系统:API 接口开发与实时数据采集教程
大数据·前端·数据库·oracle
Joker`s smile1 小时前
Chrome安装老版本、不同版本,自制便携版本用于前端调试
前端·chrome
weixin_416639971 小时前
爬虫工程师Chrome开发者工具简单介绍
前端·chrome·爬虫
我是如子啊1 小时前
【解决“此扩展可能损坏”】Edge浏览器(chrome系列通杀))扩展损坏?一招保留数据快速修复
前端·chrome·edge
灵性花火1 小时前
Qt的前端和后端过于耦合(0/7)
开发语言·前端·qt
孤水寒月5 小时前
基于HTML的悬窗可拖动记事本
前端·css·html
祝余呀5 小时前
html初学者第一天
前端·html
耶啵奶膘8 小时前
uniapp+firstUI——上传视频组件fui-upload-video
前端·javascript·uni-app
视频砖家8 小时前
移动端Html5播放器按钮变小的问题解决方法
前端·javascript·viewport功能