MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SvelteKit 路由错误处理:自定义 404 页面与错误捕获

2023-01-057.9k 阅读

SvelteKit 路由错误处理基础概念

在前端开发中,错误处理是确保应用程序稳健运行的关键环节。当用户访问不存在的页面或在路由过程中发生意外错误时,提供友好且合理的反馈至关重要。SvelteKit 作为基于 Svelte 的全栈框架,为开发者提供了强大的路由错误处理机制,使得自定义 404 页面以及捕获各类路由相关错误变得相对容易。

路由错误通常分为两种主要类型:一种是页面不存在导致的 404 错误,另一种是在路由处理过程中由于代码逻辑问题或外部依赖问题引发的其他错误。在 SvelteKit 中,针对这两类错误,有不同但又相辅相成的处理方式。

自定义 404 页面

创建自定义 404 页面的目录结构

在 SvelteKit 项目中,创建自定义 404 页面非常直观。项目的 src/routes 目录是路由定义的核心位置。我们需要在这个目录下创建一个名为 404.svelte 的文件。这个文件的命名是 SvelteKit 约定俗成的,只要存在这个文件,SvelteKit 就会将其识别为自定义的 404 页面。

例如,假设我们有一个简单的 SvelteKit 项目结构如下:

my - sveltekit - project/
├── src/
│   ├── routes/
│   │   ├── +layout.svelte
│   │   ├── index.svelte
│   │   ├── about.svelte
│   │   └── 404.svelte
│   └── app.js
├── package.json
└── rollup.config.js

在上述结构中,404.svelte 文件就是我们自定义的 404 页面。

404.svelte 页面的内容编写

404.svelte 本质上就是一个普通的 Svelte 组件。我们可以在其中编写 HTML、CSS 和 Svelte 特有的脚本代码,来设计出符合项目风格的 404 页面。

以下是一个简单的 404.svelte 示例:

<script>
    // 可以在这里添加一些逻辑,比如页面加载时的动画等
</script>

<style>
    body {
        font - family: Arial, sans - serif;
        text - align: center;
        padding: 50px;
    }

    h1 {
        font - size: 3em;
        color: #ff0000;
    }

    p {
        font - size: 1.5em;
    }
</style>

<body>
    <h1>404 - Page Not Found</h1>
    <p>The page you are looking for could not be found.</p>
    <a href="/">Go back to the homepage</a>
</body>

在这个示例中,我们设置了页面的基本样式,显示一个醒目的“404 - Page Not Found”标题,一段提示信息,并提供了一个返回首页的链接。

错误捕获

捕获路由加载过程中的错误

在 SvelteKit 中,我们可以在路由组件中捕获加载过程中发生的错误。每个路由组件都可以导出一个 load 函数,这个函数用于在页面加载时获取数据。如果在这个 load 函数中发生错误,我们可以通过特定的机制捕获并处理它。

假设我们有一个 products.svelte 路由组件,它从一个 API 获取产品列表数据:

<script context="module">
    export async function load({ fetch }) {
        try {
            const response = await fetch('/api/products');
            if (!response.ok) {
                throw new Error('Failed to fetch products');
            }
            const data = await response.json();
            return { products: data };
        } catch (error) {
            // 捕获错误
            return {
                status: 500,
                error: 'An error occurred while fetching products'
            };
        }
    }
</script>

<script>
    export let data;
    if (data.error) {
        // 处理错误,比如显示错误信息
        console.error(data.error);
    } else {
        // 正常渲染产品列表
        const { products } = data;
    }
</script>

{#if data.error}
    <p>{data.error}</p>
{:else}
    <ul>
        {#each products as product}
            <li>{product.name}</li>
        {/each}
    </ul>
{/if}

在上述代码中,load 函数使用 fetch/api/products 获取数据。如果请求失败,它会抛出一个错误,并在 catch 块中返回一个包含 statuserror 信息的对象。在组件的脚本部分,我们根据 data.error 是否存在来决定是显示错误信息还是渲染产品列表。

全局错误捕获

除了在单个路由组件中捕获错误,SvelteKit 还提供了全局错误捕获的能力。我们可以在 src/app.js 文件中定义一个 handleError 函数来全局处理路由错误。

以下是 src/app.js 的示例代码:

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

export const handleError: handleError = ({ error, event }) => {
    console.error('Global error:', error);
    return {
        message: 'An unexpected error occurred. Please try again later.',
        status: 500
    };
};

在这个 handleError 函数中,我们接收 errorevent 参数。error 是发生的错误对象,event 包含了关于当前请求的信息。我们可以在这里记录错误日志,然后返回一个统一的错误响应,这个响应会被应用到发生错误的页面上。

404 页面与错误捕获的结合使用

基于错误类型显示不同的 404 提示

有时候,我们可能希望根据错误的具体类型来显示不同的 404 提示信息。例如,如果是用户手动输入了错误的 URL 导致 404,和由于服务器端数据错误导致的页面无法正常显示,我们可以给出不同的友好提示。

首先,在全局 handleError 函数中,我们可以根据错误类型进行分类:

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

export const handleError: handleError = ({ error, event }) => {
    let customMessage;
    if (error.status === 404) {
        customMessage = 'The page you requested was not found. Maybe you mistyped the URL?';
    } else {
        customMessage = 'An unexpected error occurred. Please try again later.';
    }
    console.error('Global error:', error);
    return {
        message: customMessage,
        status: error.status || 500
    };
};

然后,在 404.svelte 页面中,我们可以接收并显示这个自定义的错误信息:

<script>
    export let data;
</script>

<style>
    body {
        font - family: Arial, sans - serif;
        text - align: center;
        padding: 50px;
    }

    h1 {
        font - size: 3em;
        color: #ff0000;
    }

    p {
        font - size: 1.5em;
    }
</style>

<body>
    <h1>404 - Page Not Found</h1>
    <p>{data.message}</p>
    <a href="/">Go back to the homepage</a>
</body>

这样,当发生 404 错误时,404.svelte 页面会显示更具针对性的错误提示信息。

处理嵌套路由中的错误

在 SvelteKit 中,嵌套路由是很常见的场景。例如,我们可能有一个博客应用,其中有文章详情页面,其路由可能是 /blog/[slug],并且这个页面可能有嵌套的评论部分,其路由可能是 /blog/[slug]/comments

当在嵌套路由中发生错误时,错误处理机制同样适用。假设在 /blog/[slug]/commentsload 函数中获取评论数据时发生错误:

<script context="module">
    export async function load({ params, fetch }) {
        try {
            const response = await fetch(`/api/blog/${params.slug}/comments`);
            if (!response.ok) {
                throw new Error('Failed to fetch comments');
            }
            const data = await response.json();
            return { comments: data };
        } catch (error) {
            return {
                status: 500,
                error: 'An error occurred while fetching comments'
            };
        }
    }
</script>

<script>
    export let data;
    if (data.error) {
        console.error(data.error);
    } else {
        const { comments } = data;
    }
</script>

{#if data.error}
    <p>{data.error}</p>
{:else}
    <ul>
        {#each comments as comment}
            <li>{comment.text}</li>
        {/each}
    </ul>
{/if}

在这个例子中,当获取评论数据失败时,组件会捕获错误并显示相应的错误信息。如果在嵌套路由的父级组件(如 /blog/[slug])也有错误处理逻辑,那么错误会按照一定的规则进行传递和处理。

如果父级组件的 load 函数中也有错误处理:

<script context="module">
    export async function load({ params, fetch }) {
        try {
            const articleResponse = await fetch(`/api/blog/${params.slug}`);
            if (!articleResponse.ok) {
                throw new Error('Failed to fetch article');
            }
            const articleData = await articleResponse.json();

            const commentsResponse = await fetch(`/api/blog/${params.slug}/comments`);
            if (!commentsResponse.ok) {
                throw new Error('Failed to fetch comments');
            }
            const commentsData = await commentsResponse.json();

            return { article: articleData, comments: commentsData };
        } catch (error) {
            return {
                status: 500,
                error: 'An error occurred while fetching article or comments'
            };
        }
    }
</script>

<script>
    export let data;
    if (data.error) {
        console.error(data.error);
    } else {
        const { article, comments } = data;
    }
</script>

{#if data.error}
    <p>{data.error}</p>
{:else}
    <h1>{article.title}</h1>
    <p>{article.content}</p>
    <h2>Comments</h2>
    <ul>
        {#each comments as comment}
            <li>{comment.text}</li>
        {/each}
    </ul>
{/if}

在这种情况下,如果子路由(/blog/[slug]/comments)的 load 函数发生错误,父级组件的 catch 块会捕获到这个错误,并进行统一处理。如果父级组件希望将错误传递给全局错误处理,也可以不进行本地处理,让错误向上冒泡。

与后端集成时的错误处理

后端返回 404 状态码的处理

在全栈应用中,后端可能会返回 404 状态码,例如当请求的资源在数据库中不存在时。SvelteKit 可以很好地处理这种情况。

假设后端 API 对于不存在的产品返回 404 状态码,在前端的路由 products.svelteload 函数中:

<script context="module">
    export async function load({ fetch }) {
        const response = await fetch('/api/products');
        if (response.status === 404) {
            return {
                status: 404,
                error: 'No products found'
            };
        }
        if (!response.ok) {
            throw new Error('Failed to fetch products');
        }
        const data = await response.json();
        return { products: data };
    }
</script>

<script>
    export let data;
    if (data.error) {
        if (data.status === 404) {
            console.log('404 from backend:', data.error);
        } else {
            console.error(data.error);
        }
    } else {
        const { products } = data;
    }
</script>

{#if data.error}
    {#if data.status === 404}
        <p>{data.error}</p>
    {:else}
        <p>An error occurred while fetching products</p>
    {/if}
{:else}
    <ul>
        {#each products as product}
            <li>{product.name}</li>
        {/each}
    </ul>
{/if}

在这个例子中,我们检查后端返回的状态码,如果是 404,就返回一个带有 404 状态和相应错误信息的对象,组件根据这个信息显示合适的提示。

后端错误传递与前端统一处理

后端在发生其他错误时,也可以将错误信息传递给前端,以便前端进行统一处理。例如,后端在处理复杂业务逻辑时发生错误,可以返回一个包含错误详情的 JSON 数据。

假设后端返回的错误响应如下:

{
    "error": "Internal server error: Database connection lost",
    "status": 500
}

在前端的 load 函数中:

<script context="module">
    export async function load({ fetch }) {
        const response = await fetch('/api/some - complex - operation');
        if (!response.ok) {
            const errorData = await response.json();
            return {
                status: errorData.status,
                error: errorData.error
            };
        }
        const data = await response.json();
        return { result: data };
    }
</script>

<script>
    export let data;
    if (data.error) {
        console.error(data.error);
    } else {
        const { result } = data;
    }
</script>

{#if data.error}
    <p>{data.error}</p>
{:else}
    <p>{result}</p>
{/if}

通过这种方式,后端的错误可以准确地传递到前端,并由前端按照统一的错误处理逻辑进行展示或进一步处理。

测试错误处理功能

单元测试路由组件的错误处理

在 SvelteKit 项目中,我们可以使用测试框架如 Vitest 来测试路由组件的错误处理逻辑。假设我们要测试 products.svelte 组件的错误处理:

首先,安装 Vitest 和相关的测试库:

npm install --save - dev vitest @testing - library/svelte

然后,创建一个测试文件 products.test.js

import { render, screen } from '@testing - library/svelte';
import Products from './products.svelte';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

describe('Products component error handling', () => {
    let originalFetch;

    beforeEach(() => {
        originalFetch = global.fetch;
        global.fetch = vi.fn();
    });

    afterEach(() => {
        global.fetch = originalFetch;
    });

    it('should display error message when fetch fails', async () => {
        (global.fetch as any).mockRejectedValue(new Error('Failed to fetch products'));
        render(Products);
        const errorElement = await screen.findByText('An error occurred while fetching products');
        expect(errorElement).toBeInTheDocument();
    });

    it('should display 404 error message when backend returns 404', async () => {
        (global.fetch as any).mockResolvedValue({
            status: 404,
            json: async () => ({ error: 'No products found' })
        });
        render(Products);
        const errorElement = await screen.findByText('No products found');
        expect(errorElement).toBeInTheDocument();
    });
});

在这个测试文件中,我们使用 vitest@testing - library/svelte 来模拟 fetch 的不同错误情况,并验证组件是否正确显示相应的错误信息。

集成测试全局错误处理

对于全局错误处理的集成测试,我们可以使用 Cypress 这样的端到端测试框架。

首先,安装 Cypress:

npm install --save - dev cypress

然后,在 cypress/integration 目录下创建一个测试文件,比如 error - handling.spec.js

describe('Global error handling', () => {
    it('should display global error message on error', () => {
        cy.visit('/non - existent - page');
        cy.contains('An unexpected error occurred. Please try again later.');
    });
});

在这个测试中,我们访问一个不存在的页面,模拟发生错误,然后验证全局错误处理设置的错误信息是否正确显示在页面上。通过这样的测试,可以确保整个应用在错误处理方面的稳定性和正确性。

优化错误处理的用户体验

错误页面的动画与交互设计

除了显示简单的错误信息,我们可以通过添加动画和交互元素来提升错误页面的用户体验。在 404.svelte 页面中,我们可以使用 Svelte 的过渡和动画功能。

例如,添加一个淡入动画:

<script>
    import { fade } from'svelte/transition';
    let errorMessage = 'The page you are looking for could not be found.';
</script>

<style>
    body {
        font - family: Arial, sans - serif;
        text - align: center;
        padding: 50px;
    }

    h1 {
        font - size: 3em;
        color: #ff0000;
    }

    p {
        font - size: 1.5em;
    }
</style>

<body>
    <h1>404 - Page Not Found</h1>
    <p transition:fade>{errorMessage}</p>
    <a href="/">Go back to the homepage</a>
</body>

这样,当错误页面加载时,错误信息会以淡入的动画效果呈现,给用户一种更流畅的视觉体验。

我们还可以添加一些交互元素,比如一个搜索框,让用户可以在错误页面上尝试搜索他们想要的内容:

<script>
    import { fade } from'svelte/transition';
    let errorMessage = 'The page you are looking for could not be found.';
    let searchQuery = '';
    const handleSearch = () => {
        // 这里可以添加搜索逻辑,比如跳转到搜索结果页面
        console.log('Searching for:', searchQuery);
    };
</script>

<style>
    body {
        font - family: Arial, sans - serif;
        text - align: center;
        padding: 50px;
    }

    h1 {
        font - size: 3em;
        color: #ff0000;
    }

    p {
        font - size: 1.5em;
    }

   .search - input {
        padding: 10px;
        width: 300px;
        margin - bottom: 20px;
    }

   .search - button {
        padding: 10px 20px;
        background - color: #007bff;
        color: white;
        border: none;
        cursor: pointer;
    }
</style>

<body>
    <h1>404 - Page Not Found</h1>
    <p transition:fade>{errorMessage}</p>
    <input type="text" bind:value={searchQuery} class="search - input" placeholder="Search for what you need">
    <button on:click={handleSearch} class="search - button">Search</button>
    <a href="/">Go back to the homepage</a>
</body>

通过这些动画和交互设计,可以让用户在遇到错误时,感受到应用更加友好和智能。

错误提示的本地化

如果应用需要面向多种语言的用户,错误提示的本地化就非常重要。SvelteKit 可以结合 i18n 库来实现错误提示的本地化。

首先,安装 svelte - i18n

npm install svelte - i18n

然后,在项目中配置 i18n。假设我们有两个语言文件 en.jsonzh.json,分别用于英文和中文的错误提示: en.json

{
    "404_error": "The page you are looking for could not be found.",
    "general_error": "An unexpected error occurred. Please try again later."
}

zh.json

{
    "404_error": "您查找的页面不存在。",
    "general_error": "发生了意外错误,请稍后再试。"
}

src/routes/404.svelte 中:

<script>
    import { t, useTranslation } from'svelte - i18n';
    useTranslation();
</script>

<style>
    body {
        font - family: Arial, sans - serif;
        text - align: center;
        padding: 50px;
    }

    h1 {
        font - size: 3em;
        color: #ff0000;
    }

    p {
        font - size: 1.5em;
    }
</style>

<body>
    <h1>404 - Page Not Found</h1>
    <p>{$t('404_error')}</p>
    <a href="/">Go back to the homepage</a>
</body>

通过这种方式,根据用户设置的语言,错误提示会以相应的语言显示,大大提升了不同语言用户的体验。

通过以上对 SvelteKit 路由错误处理中自定义 404 页面与错误捕获的详细介绍,包括从基础概念、具体实现、与后端集成、测试以及优化用户体验等方面,开发者可以构建出更加健壮、友好的前端应用程序。无论是小型项目还是大型复杂应用,合理的错误处理机制都是确保应用稳定性和用户满意度的关键因素。在实际开发中,根据项目的具体需求和场景,灵活运用这些技术,可以有效提升开发效率和应用质量。