Migrate my website to SvelteKit2

为什么

之前我的博客使用的是 sveltejs 1.x 相关的周边,但在一次更新部署时遇到了依赖兼容性问题:

Bash
@sveltejs/vite-plugin-svelte directly depended upon svelte, throw error

SyntaxError: Named export 'createFilter' not found. The requested module 'vite' is a CommonJS module%2C which may not support all module.exports as named exports.

此外,目前在 Vercel 上用的 Node.js 版本仍然是 16.x,Vercel 在 2025 年 1 月 31 日将不再支持构建部署:

Bash
Error: Node.js version 16.x has reached End-of-Life. Deployments created on or after 2025-01-31 will fail to build. Please set Node.js Version to 20.x in your Project Settings to use Node.js 20

所以是时候做一波升级了。

怎么做

核心还是参考 svelte 官方的升级指南 svelte/v4-migration-guide,再结合具体遇到的问题补充修复。

1.依赖升级

  • svelte 升级到 4.x。
  • @sveltejs/kit 升级到 2.x,并添加 @sveltejs/vite-plugin-svelte 取代了内置 Vite 处理逻辑。
  • vite 升级到 5.x,当然后续我们需要适配 Vite 5 的变更。
  • 其他依赖如 typescriptsvelte-preprocess、svelte 部署相关的 adapter 也升级到较新版本。
JSON
"devDependencies": {
-   "@sveltejs/adapter-auto": "1.0.0-next.50",
-   "@sveltejs/adapter-static": "1.0.0-next.29",
-   "@sveltejs/adapter-vercel": "1.0.0-next.58",
-   "@sveltejs/kit": "1.0.0-next.350",
+   "@sveltejs/adapter-auto": "^3.2.4",
+   "@sveltejs/adapter-static": "^3.0.4",
+   "@sveltejs/adapter-vercel": "^4.0.5",
+   "@sveltejs/kit": "^2.5.27",
+   "@sveltejs/vite-plugin-svelte": "^3.1.2",
-   "svelte": "^3.44.0",
-   "svelte-preprocess": "^4.10.5",
-   "vite": "^2.9.1",
+   "svelte": "^4.2.19",
+   "svelte-preprocess": "^5.0.3",
+   "vite": "^5.4.5",
+   "typescript": "^5.x"
}

相应的,需要对 svelte.config.js 更新一些配置,主要包括:

  • 简化 prerender 的配置 具体的 prerender 会在页面中配置。
  • vercel-adaptor 开启 runtime: 'edge'
  • 移除 vite 配置,通过 vite.config.ts 单独配置。
JavaScript
    adapter: vercel({
-     edge: false,
+     runtime: 'edge',

    prerender: {
-     default: true,
    },
-    vite:{}

单独移出的 vite.config.ts 很简单:

TypeScript
import path from 'path';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
    plugins: [sveltekit()],
    resolve: {
        alias: {
            $draw: path.resolve('./draw'),
            $blog: path.resolve('./blog'),
            $note: path.resolve('./note'),
            $src: path.resolve('./src'),
        }
    },
    // allows vite access to ./posts
    server: {
    fs: {
        allow: ['./']
    }
    },
});

此外,为了适配新的 Svelte 版本,我调整了 package.json 里的 dev 脚本,使其兼容新的 vite 配置。

  • svelte-kit 命令替换为 vite,适配新版本。
  • svelte-kit sync 已经不再默认需要。

旧版 SvelteKit 1

JSON
"scripts": {
  "dev": "svelte-kit dev",
  "build": "svelte-kit build",
  "preview": "svelte-kit preview",
  "check": "svelte-check --tsconfig tsconfig.json",
  "prepare": "svelte-kit sync"
}

新版 SvelteKit 2

JSON
"scripts": {
  "dev": "vite dev",
  "build": "vite build",
  "preview": "vite preview",
  "check": "svelte-check"
}

2.路由调整

由于 SvelteKit 基于文件系统的路由规则上发生了 breaking change(具体可看SvelteKit Routing)我的路由结构也需要调整:

  • 采用 嵌套路由,优化页面组件结构。
  • 使用新的 PageServerLoad 取代旧版 load 方法。
  • 配置 PageConfig 以支持 SSG(静态页面生成)。

组件文件命名变更:

旧版 (SvelteKit 1) 新版 (SvelteKit 2)
_layout.svelte +layout.svelte
_error.svelte +error.svelte
index@page.svelte +page.svelte

load 函数变更:

旧版 SvelteKit 1

HTML, XML
<script context="module">
  export async function load({ fetch }) {
    const res = await fetch('/api/posts');
    return { props: { posts: await res.json() } };
  }
</script>

<script>
  export let posts;
</script>

新版 SvelteKit 2

HTML, XML
<script>
  export let data;
</script>

<script>
  const { posts } = data;
</script>

loadcontext="module" 变为 PageServerLoader +page.server.js 处理:

JavaScript
export async function load({ fetch }) {
  const res = await fetch('/api/posts');
  return { posts: await res.json() };
}

前面提到 sveltekit 支持对每个页面单独配置 prerender,理论上说对于博客这种静态没太多交互的站点,每个页面都应该配置成 prerender,构建和部署时就生成页面,也就是 SSG,而不是需要 server-renderSSR。但由于我不排除未来还会增加一些动态内容,在 SSR 的 manifest 中保留页面记录,所以我的配置是 export const prerender = 'auto';

JavaScript
/** @type {import('./$types').PageServerLoad} */
export async function load({ fetch }) {
    const fethcDraws = await fetch('/api/draws-lite');
    const fethcPosts = await fetch('/api/posts-lite');
    const liteDraws = await fethcDraws.json();
    const litePosts = await fethcPosts.json();
    return {
		liteDraws,
		litePosts,
    };
}

export const prerender = 'auto';

rss 之类的纯静态生成页面的路由也需要改造,改造方式就是 增加 src/routes/rss.xml/+server.js

JavaScript
export const get = async () => {
  const sortedPosts = await getAllPosts({ sorted: true });

  const headers = {
    'Cache-Control': 'max-age=0, s-max-age=3600',
    'Content-Type': 'application/xml',
  };

  const body = xml(sortedPosts);

-  return {
-    headers,
-    body,
-  };
+  return new Response(body, {
+    headers,
+  });

+ export const prerender = true;
}

重点在于配置 prerender 让他构建时生成,同时让 server handler 返回 Response 对象

3.Vercel 部署问题

在尝试 ssg 时,发现 Vercel 对 PageServerLoad 的支持有限,导致 searchParams 不能在 load 阶段访问,因为它在服务器端不可用。所以只好把涉及到 url 传参的接口全部改掉:

更新前的代码:

JavaScript
export async function GET({ url}) {
  const sorted = url.searchParams.get('sorted');
  const sortedDraws = await getAllDraws({ sorted });
  return new Response(JSON.stringify(sortedDraws));
}

更新后的代码:

JavaScript
export async function GET() {
  const sortedDraws = await getAllDraws({ sorted: true });
  return new Response(JSON.stringify(sortedDraws));
}

此外,我还增加了性能监控: src/routes/+layout.svelte

JavaScript
+ import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit';

+ injectSpeedInsights();

总结

本次博客技术栈升级的核心变化:

  • 依赖升级到最新的 SvelteKit,避免旧版兼容性问题。
  • 采用新的 PageServerLoad 和 PageConfig 方式优化 SSG 支持,并保留未来 SSR 的可拓展⭐️。
  • 解决 searchParams 在服务器端不可用的问题。
  • 路由结构调整,提高代码可维护性。
  • 优化 Vercel 部署流程。