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

总结

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

相关推荐
zengyuhan5031 小时前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
醉方休1 小时前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running1 小时前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔1 小时前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户4445543654261 小时前
Android的自定义View
前端
WILLF1 小时前
HTML iframe 标签
前端·javascript
枫,为落叶2 小时前
Axios使用教程(一)
前端
小章鱼学前端2 小时前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah2 小时前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript
流星稍逝2 小时前
手搓一个简简单单进度条
前端