---
title: "无加载不是速度，是连续感"
description: "一个 300ms 的白屏比一个 800ms 的骨架屏更让人焦虑。这篇讲怎么用 SSG、预取和 view transition 把页面跳转做成「一瞬间的事」。"
canonical_url: "https://www.kemiaosw.top/posts/no-loading-feel"
---

> Index of every page on this site: [llms.txt](https://www.kemiaosw.top/llms.txt)

先做一个思想实验。

方案 A：点击链接后，页面白屏 300ms，然后新页面完整出现。

方案 B：点击链接后，立刻显示骨架屏，800ms 后内容填进来。

哪个更快？A。

哪个感觉更快？**A。**

骨架屏不是为了让页面"看起来在加载"，它是在承认"我在加载"。而这个承认本身，就是一种延迟信号。

## 真正的「无加载」是什么

无加载的意思是：**用户从未看到"等待"这个状态。**

实现它需要三个条件同时成立：

1. 页面在点击之前就已经准备好了
2. 切换过程不出现空白帧
3. 没有任何 loading 指示器

## 条件一：提前准备

SSG（静态生成）把这件事做到了极致：所有页面在构建时就变成了 HTML 文件。

点击链接时，浏览器请求的是一个已经存在的静态文件——不是"等服务器查询数据库再渲染"。

但即使如此，仍然有一次网络往返。所以还需要**预取**：

```html
<!-- Nuxt 的 NuxtLink 默认开启 prefetch -->
<NuxtLink to="/archive">归档</NuxtLink>
```

Nuxt 会在链接进入视口时预取目标页面的 payload。用户真正点击时，数据已经在内存里了。

**关键细节**：外链和友链要显式关闭预取，否则会预取一堆无关域名。

```html
<NuxtLink to="/links" no-prefetch>友链</NuxtLink>
```

## 条件二：不出现空白帧

即使数据准备好了，页面切换仍然会有一次 DOM 重建。

如果没有过渡，你会看到：旧页面消失 → 空白 → 新页面出现。

那"空白"可能只有 16ms（一帧），但眼睛能看见。

解法是 `out-in` 模式的页面过渡：

```ts
// nuxt.config.ts
app: {
  pageTransition: { name: 'page', mode: 'out-in' },
}
```

```css
.page-enter-active,
.page-leave-active {
  transition: opacity 200ms ease-out, transform 200ms ease-out;
}

.page-enter-from {
  opacity: 0;
  transform: translateY(4px);
}

.page-leave-to {
  opacity: 0;
}
```

200ms 的淡入淡出。旧页面淡出的同时新页面淡入，中间没有空白帧。

**为什么是 200ms？** 低于 150ms 感知不到，高于 300ms 开始觉得慢。200ms 是"有过渡但不等待"的窗口。

## 条件三：删掉 loading 指示器

这一条最反直觉。

大部分项目会加一个顶部进度条（NuxtLoadingIndicator）：

```vue
<!-- 删掉这个 -->
<NuxtLoadingIndicator />
```

进度条的存在本身就在告诉用户："可能要等一下"。

当你的页面跳转真的在 200ms 内完成时，进度条会**闪一下**——比没有更糟。

所以我把 `NuxtLoadingIndicator` 完全删掉了，靠 view transition 自然过渡。

## 一个真实的对比

改之前（有骨架屏 + 进度条）：

```text
点击 → 进度条出现 → 骨架屏 → 内容填入 → 进度条消失
总时长：约 600ms，用户全程在"等待"
```

改之后（SSG + 预取 + 过渡）：

```text
点击 → 淡出 → 淡入
总时长：约 200ms，用户感知为"瞬间"
```

## 什么情况下做不到

诚实地说，这套方案有三个前提：

**一、内容必须是静态的。** 如果页面需要实时数据（股票、聊天），SSG 帮不上忙。

**二、页面数量要可控。** 全量预渲染 10 万个页面，构建要几小时。

**三、首屏没法这样优化。** 用户第一次访问仍然要下载 HTML + CSS + JS。这是物理限制。

对一个个人博客来说，这三个前提全都成立——所以它能做到真正的无加载。

## 首屏怎么办

首屏的唯一优化方向是**减小体积**。

我的做法：

- 没有第三方脚本（统计默认关闭）
- 字体分片，实际加载约 180KB
- 搜索索引懒加载（首次搜索才请求）
- 零运行时状态库（没有 Pinia）

结果：首屏 JS 约 41KB（gzip 后）。

## 一句话

> 无加载不是把加载藏起来，是**让加载根本不需要发生**。

这两者的区别在于：前者是障眼法，后者是架构。

## Sitemap

See the full [sitemap](https://www.kemiaosw.top/sitemap.md) for all pages.
