普通视图

发现新文章,点击刷新页面。
昨天 — 2025年3月31日首页

SvelteKit 最新中文文档教程(16)—— Service workers

作者 冴羽
2025年3月31日 20:41

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

Service workers

Service workers 作为代理服务端,处理应用程序内部的网络请求。这使得您的应用程序能够离线工作,但即使您不需要离线支持(或由于您正在构建的应用程序类型而无法实现它),使用 service workers 预缓存构建的 JS 和 CSS 来加快导航速度,通常也是值得的。

在 SvelteKit 中,如果您有一个 src/service-worker.js 文件(或 src/service-worker/index.js),它将被打包并自动注册。如果需要,您可以更改 service worker 的位置

如果您需要使用自己的逻辑注册 service worker 或使用其他解决方案,可以禁用自动注册。默认注册看起来类似这样:

if ('serviceWorker' in navigator) {
addEventListener('load', function () {
navigator.serviceWorker.register('./path/to/service-worker.js');
});
}

Service Worker 内部

在 service worker 内部,您可以访问 $service-worker 模块,它为您提供所有静态资源、构建文件和预渲染页面的路径。您还会获得一个应用程序版本字符串,可用于创建唯一的缓存名称,以及部署的 base 路径。如果您的 Vite 配置指定了 define(用于全局变量替换),这也将应用于 service workers 以及服务端/客户端构建。

以下示例会尽可能早的缓存构建的应用程序和 static 中的所有文件,并在访问时缓存所有其他请求。这将使每个页面在访问后都能离线工作。

// @errors: 2339
/// <reference types="@sveltejs/kit" />
import { build, files, version } from '$service-worker';

// 为此部署创建唯一的缓存名称
const CACHE = `cache-${version}`;

const ASSETS = [
...build, // 应用程序本身
...files // `static` 中的所有内容
];

self.addEventListener('install', (event) => {
// 创建新缓存并添加所有文件
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}

event.waitUntil(addFilesToCache());
});

self.addEventListener('activate', (event) => {
// 从磁盘删除以前的缓存数据
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}

event.waitUntil(deleteOldCaches());
});

self.addEventListener('fetch', (event) => {
// 忽略 POST 请求等
if (event.request.method !== 'GET') return;

async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);

// `build`/`files` 始终可以从缓存中提供服务
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);

if (response) {
return response;
}
}

// 对于其他所有内容,首先尝试网络
// 但如果我们离线,则回退到缓存
try {
const response = await fetch(event.request);

// 如果我们离线,fetch 可能返回非 Response 值
// 而不是抛出错误 - 我们不能将这个非 Response 传递给 respondWith
if (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
}

if (response.status === 200) {
cache.put(event.request, response.clone());
}

return response;
} catch (err) {
const response = await cache.match(event.request);

if (response) {
return response;
}

// 如果没有缓存,就直接报错
// 因为我们无法对这个请求做任何响应
throw err;
}
}

event.respondWith(respond());
});

[!NOTE] 缓存时要小心!在某些情况下,过时的数据可能比离线时无法获取的数据更糟糕。由于浏览器会在缓存太满时清空缓存,因此您还应该谨慎缓存大型资源,如视频文件。

在开发过程中

service worker 在生产环境中会被打包,但在开发过程中不会。因此,只有支持 service workers 中的模块 的浏览器才能在开发时使用它们。如果您手动注册 service worker,在开发时需要传递 { type: 'module' } 选项:

import { dev } from '$app/environment';

navigator.serviceWorker.register('/service-worker.js', {
type: dev ? 'module' : 'classic'
});

[!NOTE] 在开发环境中,buildprerendered 是空数组

类型安全

为 service workers 设置适当的类型需要一些手动设置。在您的 service-worker.js 中,在文件顶部添加以下内容:

/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />

const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
/// <reference types="@sveltejs/kit" />
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />

const sw = self as unknown as ServiceWorkerGlobalScope;

这会禁用对 service worker 中不可用的 DOM 类型(如 HTMLElement)的访问,并实例化正确的全局变量。将 self 重新赋值给 sw 允许您在此过程中进行类型转换(有几种方法可以做到这一点,但这是最简单的,不需要额外的文件)。在文件的其余部分使用 sw 而不是 self

对 SvelteKit 类型的引用确保 $service-worker 导入具有适当的类型定义。如果您导入 $env/static/public,您要么必须使用 // @ts-ignore 注释导入,要么添加 /// <reference types="../.svelte-kit/ambient.d.ts" /> 到引用类型中。

其他解决方案

SvelteKit 的 service worker 实现故意保持低级别。如果您需要更全功能但也更有主见的解决方案,我们建议查看像 Vite PWA 插件 这样的解决方案,它使用 Workbox。有关 service workers 的更多一般信息,我们推荐 MDN web 文档

Svelte 中文文档

点击查看中文文档:SvelteKit Service workers

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng…

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

昨天以前首页

SvelteKit 最新中文文档教程(14)—— 错误处理

作者 冴羽
2025年3月29日 10:22

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

错误处理

错误是软件开发中不可避免的事实。SvelteKit 根据错误发生的位置、错误类型以及传入请求的性质,采用不同的方式处理错误。

错误对象

SvelteKit 区分预期错误和意外错误,默认情况下这两种错误都表示为简单的 { message: string } 对象。

您可以添加额外的属性,比如 code 或跟踪 id,如下面的示例所示。(使用 TypeScript 时,这需要您重新定义 Error 类型,如 类型安全 中所述)。

预期错误

预期错误是使用从 @sveltejs/kit 导入的 error 辅助函数创建的错误:

/// file: src/routes/blog/[slug]/+page.server.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
  export function getPost(slug: string): Promise<{ title: string, content: string } | undefined>
}

// @filename: index.js
// ---cut---
import { error } from '@sveltejs/kit';
import * as db from '$lib/server/database';

/** @type {import('./$types').PageServerLoad} */
export async function load({ params }) {
  const post = await db.getPost(params.slug);

  if (!post) {
    error(404, {
      message: '未找到'
    });
  }

  return { post };
}

这会抛出一个异常,SvelteKit 会捕获该异常,并将响应状态码设置为 404,并渲染一个 +error.svelte 组件,其中 page.error 是一个对象,提供给 error(...) 的第二个参数。

<!--- file: src/routes/+error.svelte --->
<script>
  import { page } from '$app/state';
</script>

<h1>{page.error.message}</h1>

[!LEGACY] > $app/state 是在 SvelteKit 2.12 中添加的。如果您使用的是早期版本或正在使用 Svelte 4,请使用 $app/stores 代替。

如果需要,您可以向错误对象添加额外的属性...

import { error } from '@sveltejs/kit';

declare global {
  namespace App {
    interface Error {
      message: string;
      code: string;
    }
  }
}

// ---cut---
error(404, {
  message: '未找到',
  +++code: 'NOT_FOUND'+++
});

...否则,为了方便起见,您可以将字符串作为第二个参数传递:

import { error } from '@sveltejs/kit';
// ---cut---
---error(404, { message: '未找到' });---
+++error(404, '未找到');+++

[!NOTE] 在 SvelteKit 1.x 中,您必须自己 throw 这个 error

意外错误

意外错误是处理请求时发生的任何其他异常。由于这些错误可能包含敏感信息,意外错误消息和堆栈跟踪不会暴露给用户。

默认情况下,意外错误会打印到控制台(或在生产环境中打印到服务端日志),而暴露给用户的错误具有通用的形状:

{ "message": "内部错误" }

意外错误将通过 handleError hook 处理,在那里您可以添加自己的错误处理逻辑 — 例如,将错误发送到报告服务,或返回一个自定义错误对象,该对象将成为 $page.error

响应

如果错误发生在 handle+server.js 请求处理程序内部,SvelteKit 将根据请求的 Accept 头响应一个回退错误页面或错误对象的 JSON 表示。

您可以通过添加 src/error.html 文件来自定义回退错误页面:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>%sveltekit.error.message%</title>
</head>
<body>
<h1>我的自定义错误页面</h1>
<p>状态:%sveltekit.status%</p>
<p>消息:%sveltekit.error.message%</p>
</body>
</html>

SvelteKit 将用相应的值替换 %sveltekit.status%%sveltekit.error.message%

如果错误发生在渲染页面时的 load 函数内部,SvelteKit 将渲染最接近错误发生位置的 +error.svelte 组件。如果错误发生在 +layout(.server).js 中的 load 函数内部,最近的错误边界是该布局之上的 +error.svelte 文件(不是在它旁边)。

例外情况是当错误发生在根 +layout.js+layout.server.js 内部时,因为根布局通常会包含 +error.svelte 组件。在这种情况下,SvelteKit 使用回退错误页面。

类型安全

如果您使用 TypeScript 并需要自定义错误的形状,您可以通过在您的应用程序中声明一个 App.Error 接口来实现(按照惯例,在 src/app.d.ts 中,尽管它可以存在于 TypeScript 可以"看到"的任何地方):

/// file: src/app.d.ts
declare global {
  namespace App {
    interface Error {
+++code: string;
      id: string;+++
    }
  }
}

export {};

此接口始终包含 message: string 属性。

进一步阅读

Svelte 中文文档

点击查看中文文档:SvelteKit 错误处理

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng…

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

SvelteKit 最新中文文档教程(13)—— Hooks

作者 冴羽
2025年3月28日 20:43

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

Hooks

“Hooks” 是您声明的应用程序范围的函数,SvelteKit 会在响应特定事件时调用它们,让您能够对框架的行为进行更为精细的控制。

有三个 hook 文件,都是可选的:

  • src/hooks.server.js — 您的应用程序的服务端 hook
  • src/hooks.client.js — 您的应用程序的客户端 hook
  • src/hooks.js — 您的应用程序的在客户端和服务端都运行的 hook

这些模块中的代码会在应用程序启动时运行,这使得它们对初始化数据库客户端等操作很有用。

[!NOTE] 您可以通过 config.kit.files.hooks 配置这些文件的位置。

服务端 hook

以下 hook 可以添加到 src/hooks.server.js 中:

handle

这个函数在 SvelteKit 服务端每次接收到 request 时运行 — 无论是在应用程序运行时,还是在预渲染过程中 — 并决定response

它接收一个表示请求的 event 对象和一个名为 resolve 的函数,该函数渲染路由并生成一个 Response。这允许您修改响应头或响应体,或完全绕过 SvelteKit(例如,用于以编程方式实现路由)。

/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
if (event.url.pathname.startsWith('/custom')) {
return new Response('custom response');
}

const response = await resolve(event);
return response;
}

[!NOTE] 对静态资源的请求 — 包括已经预渲染的页面 — 不会由 SvelteKit 处理。

如果未实现,默认为 ({ event, resolve }) => resolve(event)

locals

要向请求中添加自定义数据(这些数据会传递给 +server.js 中的处理程序和服务端的 load 函数),可以填充 event.locals 对象,如下所示。

/// file: src/hooks.server.js
// @filename: ambient.d.ts
type User = {
name: string;
}

declare namespace App {
interface Locals {
user: User;
}
}

const getUserInformation: (cookie: string | void) => Promise<User>;

// @filename: index.js
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
event.locals.user = await getUserInformation(event.cookies.get('sessionid'));

const response = await resolve(event);
response.headers.set('x-custom-header', 'potato');

return response;
}

您可以定义多个 handle 函数,并使用sequence 辅助函数执行它们。

resolve 还支持第二个可选参数,让您能够更好地控制响应的渲染方式。该参数是一个对象,可以包含以下字段:

  • transformPageChunk(opts: { html: string, done: boolean }): MaybePromise<string | undefined> — 对 HTML 应用自定义转换。如果 done 为 true,则是最后一个块。块不保证是格式良好的 HTML(例如,它们可能包含一个元素的开始标签但没有结束标签),但它们总是会在合理的边界处分割,比如 %sveltekit.head% 或布局/页面组件。
  • filterSerializedResponseHeaders(name: string, value: string): boolean — 确定当 load 函数使用 fetch 加载资源时,哪些头部应该包含在序列化的响应中。默认情况下,不会包含任何头部。
  • preload(input: { type: 'js' | 'css' | 'font' | 'asset', path: string }): boolean — 确定应该在 <head> 标签中添加哪些文件以预加载。该方法在构建代码块时被调用,每个找到的文件都会被调用 — 例如,如果您在 +page.svelte 中有 import './styles.css,在访问该页面时,preload 将传入该 CSS 文件的解析路径进行调用。注意,在开发模式下不会调用 preload,因为它依赖于构建时的分析。预加载可以通过更早下载资源来提高性能,但如果不必要地下载太多内容也会适得其反。默认情况下,会预加载 jscss 文件。目前不会预加载 asset 文件,但我们可能会在评估反馈后添加此功能。
/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace('old', 'new'),
filterSerializedResponseHeaders: (name) => name.startsWith('x-'),
preload: ({ type, path }) => type === 'js' || path.includes('/important/')
});

return response;
}

注意,resolve(...) 永远不会抛出错误,它总是会返回一个带有适当状态码的 Promise<Response>。如果在 handle 期间其他地方抛出错误,这将被视为致命错误,SvelteKit 将根据 Accept 头部返回错误的 JSON 表示或回退错误页面 — 后者可以通过 src/error.html 自定义。您可以在这里阅读更多关于错误处理的信息。

handleFetch

这个函数允许您修改(或替换)在服务端上运行的 loadaction 函数中发生的 fetch 请求(或在预渲染期间)。

例如,当用户执行客户端导航到相应页面时,您的 load 函数可能会向公共 URL(如 https://api.yourapp.com)发出请求,但在 SSR 期间,直接访问 API 可能更有意义(绕过位于它和公共互联网之间的代理和负载均衡器)。

/// file: src/hooks.server.js
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ request, fetch }) {
if (request.url.startsWith('https://api.yourapp.com/')) {
// 克隆原始请求,但改变 URL
request = new Request(
request.url.replace('https://api.yourapp.com/', 'http://localhost:9999/'),
request
);
}

return fetch(request);
}

认证凭据

对于同源请求,除非 credentials 选项设置为 "omit",否则 SvelteKit 的 fetch 实现会转发 cookieauthorization 头部。

对于跨源请求,如果请求 URL 属于应用程序的子域,则会包含 cookie — 例如,如果您的应用程序在 my-domain.com 上,而您的 API 在 api.my-domain.com 上,cookie 将包含在请求中。

如果您的应用程序和 API 在兄弟子域上 — 例如 www.my-domain.comapi.my-domain.com — 那么属于共同父域(如 my-domain.com)的 cookie 将不会被包含,因为 SvelteKit 无法知道 cookie 属于哪个域。在这些情况下,您需要使用 handleFetch 手动包含 cookie:

/// file: src/hooks.server.js
// @errors: 2345
/** @type {import('@sveltejs/kit').HandleFetch} */
export async function handleFetch({ event, request, fetch }) {
if (request.url.startsWith('https://api.my-domain.com/')) {
request.headers.set('cookie', event.request.headers.get('cookie'));
}

return fetch(request);
}

共享 hook

以下 hook 可以同时添加到 src/hooks.server.jssrc/hooks.client.js 中:

handleError

如果在加载或渲染期间抛出意外错误,此函数将被调用,并传入 erroreventstatus 代码和 message。这允许两件事:

  • 您可以记录错误
  • 您可以生成一个安全的、显示给用户的自定义错误表示,省略敏感的详细信息,如消息和堆栈跟踪。返回的值(默认为 { message })会成为 $page.error 的值。

对于从您的代码(或您的代码调用的库代码)抛出的错误,状态将为 500,消息将为 "Internal Error"。虽然 error.message 可能包含不应暴露给用户的敏感信息,但 message 是安全的(尽管对普通用户来说没有意义)。

要以类型安全的方式向 $page.error 对象添加更多信息,您可以通过声明 App.Error 接口(必须包含 message: string,以保证合理的回退行为)来自定义预期的形状。这允许您 — 例如 — 附加一个跟踪 ID,供用户在与技术支持人员通信时引用:

/// file: src/app.d.ts
declare global {
namespace App {
interface Error {
message: string;
errorId: string;
}
}
}

export {};
/// file: src/hooks.server.js
// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
export const init: (opts: any) => void;
export const captureException: (error: any, opts: any) => void;
}

// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';

Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();

// 与 https://sentry.io/ 集成的示例
Sentry.captureException(error, {
extra: { event, errorId, status }
});

return {
message: '哎呀!',
errorId
};
}
/// file: src/hooks.client.js
// @errors: 2322 2353
// @filename: ambient.d.ts
declare module '@sentry/sveltekit' {
export const init: (opts: any) => void;
export const captureException: (error: any, opts: any) => void;
}

// @filename: index.js
// ---cut---
import * as Sentry from '@sentry/sveltekit';

Sentry.init({/*...*/})

/** @type {import('@sveltejs/kit').HandleClientError} */
export async function handleError({ error, event, status, message }) {
const errorId = crypto.randomUUID();

// 与 https://sentry.io/ 集成的示例
Sentry.captureException(error, {
extra: { event, errorId, status }
});

return {
message: '哎呀!',
errorId
};
}

[!NOTE] 在 src/hooks.client.js 中,handleError 的类型是 HandleClientError 而不是 HandleServerError,并且 event 是一个 NavigationEvent 而不是 RequestEvent

此函数不会因为预期的错误(那些使用从 @sveltejs/kit 导入的 error 函数抛出的错误)而被调用。

在开发过程中,如果由于 Svelte 代码中的语法错误而发生错误,传入的错误会附加一个 frame 属性,突出显示错误的位置。

[!NOTE] 确保 handleError 永远不会抛出错误

init

这个函数在服务端创建或应用程序在浏览器中启动时运行一次,是执行异步工作(如初始化数据库连接)的有用位置。

[!NOTE] 如果您的环境支持顶级 await,init 函数实际上与在模块顶层编写初始化逻辑没有什么不同,但一些环境 — 尤其是 Safari — 不支持。

/// file: src/hooks.server.js
import * as db from '$lib/server/database';

/** @type {import('@sveltejs/kit').ServerInit} */
export async function init() {
await db.connect();
}

[!NOTE] 在浏览器中,init 中的异步工作会延迟水合,所以要注意您在那里放什么。

通用 hook

以下 hook 可以添加到 src/hooks.js 中。通用 hook 在服务端和客户端都运行(不要与共享 hook 混淆,后者是特定环境的)。

reroute

这个函数在 handle 之前运行,允许您更改 URL 如何转换为路由。返回的路径名(默认为 url.pathname)用于选择路由及其参数。

例如,您可能有一个 src/routes/[[lang]]/about/+page.svelte 页面,它应该可以访问为 /en/about/de/ueber-uns/fr/a-propos。您可以用 reroute 来实现:

/// file: src/hooks.js
// @errors: 2345
// @errors: 2304

/** @type {Record<string, string>} */
const translated = {
'/en/about': '/en/about',
'/de/ueber-uns': '/de/about',
'/fr/a-propos': '/fr/about'
};

/** @type {import('@sveltejs/kit').Reroute} */
export function reroute({ url }) {
if (url.pathname in translated) {
return translated[url.pathname];
}
}

lang 参数将从返回的路径名正确派生。

使用 reroute 不会改变浏览器地址栏的内容,也不会改变 event.url 的值。

传输

这是一组 传输器,允许您跨服务端/客户端边界传递自定义类型 - 从 load 和 form actions 返回的类型。每个传输器都包含一个 encode 函数,该函数对服务端上的值进行编码(或对任何不是该类型的实例返回 false),以及一个相应的 decode 函数:

/// file: src/hooks.js
import { Vector } from '$lib/math';

/** @type {import('@sveltejs/kit').Transport} */
export const transport = {
Vector: {
encode: (value) => value instanceof Vector && [value.x, value.y],
decode: ([x, y]) => new Vector(x, y)
}
};

进一步阅读

Svelte 中文文档

点击查看中文文档:SvelteKit 高级路由

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng…

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

SvelteKit 最新中文文档教程(11)—— 部署 Netlify 和 Vercel

作者 冴羽
2025年3月26日 20:18

前言

Svelte,一个语法简洁、入门容易,面向未来的前端框架。

从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1

image.png

Svelte 以其独特的编译时优化机制著称,具有轻量级高性能易上手等特性,非常适合构建轻量级 Web 项目

为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。

如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

Netlify

要部署到 Netlify,请使用 adapter-netlify

当您使用 adapter-auto 时,此适配器将默认安装,但将其添加到您的项目中可以指定 Netlify 特定的选项。

用法

使用 npm i -D @sveltejs/adapter-netlify 安装,然后将适配器添加到您的 svelte.config.js

// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-netlify';

export default {
kit: {
// 显示默认选项
adapter: adapter({
// 如果为 true,将创建 Netlify Edge Function 而不是
// 使用标准的基于 Node 的函数
edge: false,

// 如果为 true,将把您的应用拆分为多个函数
// 而不是为整个应用创建单个函数。
// 如果 `edge` 为 true,则不能使用此选项
split: false
})
}
};

然后,确保在项目根目录中有一个 netlify.toml 文件。这将根据 build.publish 设置确定写入静态资源的位置,如此示例配置所示:

[build]
command = "npm run build"
publish = "build"

如果缺少 netlify.toml 文件或 build.publish 值,将使用默认值 "build"。请注意,如果您在 Netlify UI 中将发布目录设置为其他值,那么您也需要在 netlify.toml 中设置它,或使用默认值 "build"

Node 版本

新项目默认将使用当前的 Node LTS 版本。但是,如果您正在升级很久以前创建的项目,它可能会停留在旧版本上。有关手动指定当前 Node 版本的详细信息,请参阅 Netlify 文档

Netlify Edge Functions

SvelteKit 支持 Netlify Edge Functions。如果您向 adapter 函数传递 edge: true 选项,服务端渲染将在部署在靠近站点访问者的基于 Deno 的边缘函数中进行。如果设置为 false(默认值),站点将部署到基于 Node 的 Netlify Functions。

// @errors: 2307
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-netlify';

export default {
kit: {
adapter: adapter({
// 将使用基于 Deno 的 Netlify Edge Function
// 而不是使用标准的基于 Node 的函数
edge: true
})
}
};

SvelteKit 功能的 Netlify 替代方案

您可以直接使用 SvelteKit 提供的功能构建应用,而无需依赖任何 Netlify 功能。使用这些功能的 SvelteKit 版本将允许它们在开发模式下使用,通过集成测试进行测试,并在您决定切换到其他适配器时能够正常工作。但是,在某些情况下,使用这些功能的 Netlify 版本可能会更有利。例如,如果您正在将已经托管在 Netlify 上的应用迁移到 SvelteKit。

重定向规则

在编译期间,重定向规则会自动附加到您的 _redirects 文件中。(如果该文件尚不存在,它将被创建。)这意味着:

  • netlify.toml 中的 [[redirects]] 永远不会匹配,因为 _redirects 具有更高的优先级。因此,始终将您的规则放在 _redirects 文件中。
  • _redirects 不应该有任何自定义的"捕获所有"规则,如 /* /foobar/:splat。否则,由于 Netlify 只处理第一个匹配的规则,自动附加的规则将永远不会被应用。

Netlify Forms

  1. 按照这里的描述创建您的 Netlify HTML 表单,例如作为 /routes/contact/+page.svelte。(别忘了添加隐藏的 form-name input 元素!)
  2. Netlify 的构建机器人在部署时解析您的 HTML 文件,这意味着您的表单必须预渲染为 HTML 。您可以在您的 contact.svelte 中添加 export const prerender = true 来仅预渲染该页面,或设置 kit.prerender.force: true 选项来预渲染所有页面。
  3. 如果您的 Netlify 表单有一个自定义成功消息,如 <form netlify ... action="/success">,则确保相应的 /routes/success/+page.svelte 存在并已预渲染。

Netlify Functions

使用此适配器,SvelteKit 端点将作为 Netlify Functions 托管。Netlify 函数处理程序具有额外的上下文,包括 Netlify Identity 信息。您可以通过您的 hooks 和 +page.server+layout.server 端点中的 event.platform.context 字段访问此上下文。当适配器配置中的 edge 属性为 false 时,这些是serverless functions,当为 true 时,这些是edge functions

// @errors: 2705 7006
/// file: +page.server.js
export const load = async (event) => {
const context = event.platform.context;
console.log(context); // 在 Netlify 应用的函数日志中显示
};

此外,您可以通过创建一个目录并在 netlify.toml 文件中添加配置来添加您自己的 Netlify 函数。例如:

[build]
command = "npm run build"
publish = "build"

[functions]
directory = "functions"

故障排除

访问文件系统

您不能在 edge 部署中使用 fs

可以在 serverless 部署中使用它,但它不会按预期工作,因为文件不会从您的项目复制到部署中。相反,使用 $app/server 中的 read 函数来访问您的文件。read 在 edge 部署中不起作用(这在将来可能会改变)。

或者,您可以预渲染相关路由。

Vercel

要部署到 Vercel,请使用 adapter-vercel

当您使用 adapter-auto 时,这个适配器会被默认安装,但将其添加到您的项目中可以让您指定 Vercel 特定的选项。

用法

使用 npm i -D @sveltejs/adapter-vercel 安装,然后将适配器添加到您的 svelte.config.js 中:

// @errors: 2307 2345
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

export default {
kit: {
adapter: adapter({
// 可以在此处设置选项,详见下文
})
}
};

部署配置

要控制您的路由如何作为函数部署到 Vercel,您可以通过上面显示的选项或在 +server.js+page(.server).js+layout(.server).js 文件中使用 export const config 来指定部署配置。

例如,您可以将应用的某些部分部署为 Edge Functions...

/// file: about/+page.js
/** @type {import('@sveltejs/adapter-vercel').Config} */
export const config = {
runtime: 'edge'
};

...其他部分则作为 Serverless Functions(注意,在布局中指定 config 时,它会应用于所有子页面):

/// file: admin/+layout.js
/** @type {import('@sveltejs/adapter-vercel').Config} */
export const config = {
runtime: 'nodejs22.x'
};

以下选项适用于所有函数:

  • runtime'edge''nodejs18.x''nodejs20.x''nodejs22.x'。默认情况下,适配器会选择与您的项目在 Vercel 仪表板上配置的 Node 版本对应的 'nodejs<version>.x'
  • regions边缘网络区域数组(对于 serverless 函数默认为 ["iad1"])或当 runtimeedge 时为 'all'(其默认值)。注意,serverless 函数的多区域部署仅在企业版计划中支持
  • split:如果为 true,会导致路由被部署为独立函数。如果在适配器级别将 split 设置为 true,所有路由都将被部署为独立函数

此外,以下选项适用于 edge functions:

  • external:esbuild 在打包函数时应该视为外部的依赖项数组。这只应用于排除在 Node 之外不会运行的可选依赖项

以下选项适用于 serverless 函数:

  • memory:函数可用的内存量。默认为 1024 Mb,可以降低到 128 Mb 或在专业版或企业版账户中以 64Mb 为增量增加3008 Mb
  • maxDuration:函数的最大执行时长。Hobby 账户默认为 10 秒,Pro 为 15 秒,Enterprise 为 900
  • isr:配置增量静态重生成,详见下文

如果您的函数需要访问特定区域的数据,建议将它们部署在同一区域(或靠近该区域)以获得最佳性能。

图像优化

您可以设置 images 配置来控制 Vercel 如何构建您的图像。完整细节请参见图像配置参考。例如,您可以设置:

// @errors: 2300 2842 7031 1181 1005 1136 1128
/// file: svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

export default {
kit: {
adapter({
images: {
sizes: [640, 828, 1200, 1920, 3840],
formats: ['image/avif', 'image/webp'],
minimumCacheTTL: 300,
domains: ['example-app.vercel.app'],
}
})
}
};

增量静态重生成

Vercel 支持增量静态重生成 (ISR),它提供了预渲染内容的性能和成本优势,同时保持动态渲染内容的灵活性。

要为路由添加 ISR,在您的 config 对象中包含 isr 属性:

// @errors: 2664
import { BYPASS_TOKEN } from '$env/static/private';

export const config = {
isr: {
// 缓存资源将通过调用 Serverless 函数重新生成的过期时间(以秒为单位)。
// 将值设置为 `false` 表示永不过期。
expiration: 60,

// 可以在 URL 中提供的随机令牌,通过使用 __prerender_bypass=<token> cookie 请求资源来绕过缓存版本。
//
// 使用 `x-prerender-revalidate: <token>` 进行 `GET` 或 `HEAD` 请求将强制重新验证资源。
bypassToken: BYPASS_TOKEN,

// 有效查询参数列表。其他参数(如 utm 跟踪代码)将被忽略,
// 确保它们不会导致不必要的内容重新生成
allowQuery: ['search']
}
};

expiration 属性是必需的;其他都是可选的。

预渲染的页面将忽略 ISR 配置。

环境变量

Vercel 提供了一组用于部署的特定的环境变量。像其他环境变量一样,这些变量可以从 $env/static/private$env/dynamic/private 访问(有时候 — 稍后会详细说明),并且不能从它们的公共对应项访问。要从客户端访问这些变量之一:

// @errors: 2305
/// file: +layout.server.js
import { VERCEL_COMMIT_REF } from '$env/static/private';

/** @type {import('./$types').LayoutServerLoad} */
export function load() {
return {
deploymentGitBranch: VERCEL_COMMIT_REF
};
}
<!--- file: +layout.svelte --->
<script>
/** @type {{ data: import('./$types').LayoutServerData }} */
let { data } = $props();
</script>

<p>此暂存环境是从 {data.deploymentGitBranch} 部署的。</p>

由于在 Vercel 上构建时,所有这些变量在构建时和运行时之间都保持不变,我们建议使用 $env/static/private(它会静态替换变量,启用死代码消除等优化)而不是 $env/dynamic/private

版本偏差保护

当部署应用的新版本时,之前版本的资源可能不再可访问。如果用户在此时正在使用您的应用,在导航时可能会导致错误 — 这就是所谓的版本偏差。SvelteKit 通过检测由版本偏差导致的错误并触发硬重载来获取应用的最新版本来缓解这个问题,但这会导致客户端状态丢失。(您也可以通过观察 updated store 值来主动缓解它,这个值会告诉客户端何时部署了新版本。)

版本偏差保护是Vercel 的一个功能,可以将客户端请求路由到它们的原始部署。当用户访问您的应用时,会设置一个带有部署 ID 的 cookie,只要版本偏差保护处于活动状态,任何后续请求都会被路由到该部署。当他们重新加载页面时,将获得最新的部署。(updated store 不受此行为影响,因此将继续报告新的部署。)要启用它,请访问 Vercel 上项目设置的高级部分。

基于 cookie 的版本偏差保护有一个注意事项:如果用户在多个标签页中打开了您的应用的多个版本,旧版本的请求将被路由到较新的版本,这意味着它们将回退到 SvelteKit 的内置版本偏差保护。

注意事项

Vercel 函数

如果您在项目根目录的 api 目录中有 Vercel 函数,任何对 /api/* 的请求将不会由 SvelteKit 处理。您应该在您的 SvelteKit 应用中将这些实现为 API 路由,除非您需要使用非 JavaScript 语言,在这种情况下您需要确保您的 SvelteKit 应用中没有任何 /api/* 路由。

Node 版本

在某个日期之前创建的项目可能默认使用比 SvelteKit 当前要求的更旧的 Node 版本。您可以在项目设置中更改 Node 版本

故障排除

访问文件系统

您不能在 edge functions 中使用 fs

可以在 serverless 函数中使用它,但它不会按预期工作,因为文件不会从您的项目复制到您的部署中。相反,使用 $app/server 中的 read 函数来访问您的文件。read 在部署为 edge functions 的路由中不起作用(这在将来可能会改变)。

或者,您可以预渲染相关路由。

Svelte 中文文档

点击查看中文文档:

系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!

此外我还写过 JavaScript 系列TypeScript 系列React 系列Next.js 系列冴羽答读者问等 14 个系列文章, 全系列文章目录:github.com/mqyqingfeng…

欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”

❌
❌