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

总结

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

相关推荐
brzhang2 分钟前
Flutter 调用原生代码,看这篇就够了:从零教你搭起通信的桥
前端·后端·架构
袁煦丞3 分钟前
知识管理的六边形战士Trilium Notes:cpolar内网穿透实验室第520个成功挑战
前端·程序员·远程工作
失败又激情的man12 分钟前
python爬虫之数据存储
前端·数据库·python
互联网搬砖老肖13 分钟前
Web 架构之 API 安全防护:防刷、防爬、防泄漏
前端·安全·架构
小声读源码1 小时前
【技巧】dify前端源代码修改第一弹-增加tab页
前端·pnpm·next.js·dify
假客套1 小时前
2025 后端自学UNIAPP【项目实战:旅游项目】7、景点详情页面【完结】
前端·uni-app·旅游
Captaincc1 小时前
Ilya 现身多大毕业演讲:AI 会完成我们能做的一切
前端·ai编程
teeeeeeemo1 小时前
Vue数据响应式原理解析
前端·javascript·vue.js·笔记·前端框架·vue
Sahas10191 小时前
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined.
前端·javascript·vue.js
Jinxiansen02112 小时前
Vue 3 实战:【加强版】公司通知推送(WebSocket + token 校验 + 心跳机制)
前端·javascript·vue.js·websocket·typescript