琴音 發表於 2025-4-21 18:00:00

SvelteKit 最新中文文档教程(22)—— 最佳实践之无障碍与 SEO

<h2 id="前言">前言</h2>
<p>Svelte,一个语法简洁、入门容易,面向未来的前端框架。</p>
<p>从 Svelte 诞生之初,就备受开发者的喜爱,根据统计,<strong>从 2019 年到 2024 年,连续 6 年一直是开发者最感兴趣的前端框架 No.1</strong>:</p>
<p><img src="https://yayujs-blog.oss-cn-beijing.aliyuncs.com/405488775-48df16b1-939c-489b-8d52-6071869893f0.png"></p>
<p>Svelte 以其独特的编译时优化机制著称,具有<strong>轻量级</strong>、<strong>高性能</strong>、<strong>易上手</strong>等特性,<strong>非常适合构建轻量级 Web 项目</strong>。</p>
<p>为了帮助大家学习 Svelte,我同时搭建了 Svelte 最新的中文文档站点。</p>
<p>如果需要进阶学习,也可以入手我的小册《Svelte 开发指南》,语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p>
<h2 id="无障碍">无障碍</h2>
<p>SvelteKit 默认努力为您的应用程序提供一个无障碍的平台。Svelte 的编译时无障碍检查也将适用于您构建的任何 SvelteKit 应用程序。</p>
<p>以下是 SvelteKit 内置无障碍功能的工作原理,以及您需要做什么来帮助这些功能尽可能好地工作。请记住,虽然 SvelteKit 提供了无障碍的基础,但您仍然要负责确保您的应用程序代码是无障碍的。如果您是无障碍新手,请参阅本指南的"进一步阅读"部分获取更多资源。</p>
<p>我们认识到实现无障碍可能很困难。如果您想就 SvelteKit 处理无障碍的方式提出改进建议,请提交 GitHub issue。</p>
<h3 id="路由公告">路由公告</h3>
<p>在传统的服务端渲染应用程序中,每次导航(例如点击 <code>&lt;a&gt;</code> 标签)都会触发完整的页面重载。当这种情况发生时,屏幕阅读器和其他辅助技术会读出新页面的标题,让用户理解页面已经改变。</p>
<p>由于 SvelteKit 中页面之间的导航无需重新加载页面(称为客户端路由),SvelteKit 在页面上注入了一个实时区域,在每次导航后读出新页面名称。它通过检查 <code>&lt;title&gt;</code> 元素来确定要宣布的页面名称。</p>
<p>因此,您应用程序中的每个页面都应该有一个独特的、描述性的标题。在 SvelteKit 中,您可以通过在每个页面上放置 <code>&lt;svelte:head&gt;</code> 元素来实现:</p>
<pre><code class="language-svelte">&lt;!--- file: src/routes/+page.svelte ---&gt;
&lt;svelte:head&gt;
        &lt;title&gt;待办事项列表&lt;/title&gt;
&lt;/svelte:head&gt;
</code></pre>
<p>这将使屏幕阅读器和其他辅助技术能够在导航发生后识别新页面。提供描述性标题对于 SEO 也很重要。</p>
<h3 id="焦点管理">焦点管理</h3>
<p>在传统的服务端渲染应用程序中,每次导航都会将焦点重置到页面顶部。这确保了使用键盘或屏幕阅读器浏览网页的人可以从页面开始处开始交互。</p>
<p>为了在客户端路由期间模拟这种行为,SvelteKit 在每次导航和增强型表单提交后会将焦点设置在 <code>&lt;body&gt;</code> 元素上。有一个例外 - 如果存在带有 <code>autofocus</code> 属性的元素,SvelteKit 将转而聚焦该元素。使用该属性时,请确保考虑对辅助技术的影响。</p>
<p>如果您想自定义 SvelteKit 的焦点管理,可以使用 <code>afterNavigate</code> 钩子:</p>
<pre><code class="language-js">/// &lt;reference types="@sveltejs/kit" /&gt;
// ---cut---
import { afterNavigate } from '$app/navigation';

afterNavigate(() =&gt; {
        /** @type {HTMLElement | null} */
        const to_focus = document.querySelector('.focus-me');
        to_focus?.focus();
});
</code></pre>
<p>您也可以使用 <code>goto</code> 函数以编程方式导航到不同的页面。默认情况下,这将具有与点击链接相同的客户端路由行为。但是,<code>goto</code> 也接受一个 <code>keepFocus</code> 选项,该选项将保留当前聚焦的元素,而不是重置焦点。如果启用此选项,请确保当前聚焦的元素在导航后仍然存在于页面上。如果该元素不再存在,用户的焦点将丢失,这会让辅助技术用户感到困惑。</p>
<h3 id="lang-属性">"lang" 属性</h3>
<p>默认情况下,SvelteKit 的页面模板将文档的默认语言设置为英语。如果您的内容不是英语,您应该更新 <code>src/app.html</code> 中的 <code>&lt;html&gt;</code> 元素,使其具有正确的 <code>lang</code> 属性。这将确保任何阅读文档的辅助技术使用正确的发音。例如,如果您的内容是德语,您应该将 <code>app.html</code> 更新为:</p>
<pre><code class="language-html">/// file: src/app.html
&lt;html lang="de"&gt;&lt;/html&gt;
</code></pre>
<p>如果您的内容提供多种语言,您应该根据当前页面的语言设置 <code>lang</code> 属性。您可以使用 SvelteKit 的 handle hook来实现:</p>
<pre><code class="language-html">/// file: src/app.html
&lt;html lang="%lang%"&gt;&lt;/html&gt;
</code></pre>
<pre><code class="language-js">/// file: src/hooks.server.js
/**
* @param {import('@sveltejs/kit').RequestEvent} event
*/
function get_lang(event) {
        return 'en';
}
// ---cut---
/** @type {import('@sveltejs/kit').Handle} */
export function handle({ event, resolve }) {
        return resolve(event, {
                transformPageChunk: ({ html }) =&gt; html.replace('%lang%', get_lang(event))
        });
}
</code></pre>
<h3 id="进一步阅读">进一步阅读</h3>
<p>在大多数情况下,构建无障碍 SvelteKit 应用程序与构建无障碍 Web 应用程序是一样的。您应该能够将以下通用无障碍资源中的信息应用到您构建的任何 Web 体验中:</p>
<ul>
<li>MDN Web 文档:无障碍</li>
<li>A11y 项目</li>
<li>如何满足 WCAG(快速参考)</li>
</ul>
<h2 id="seo">SEO</h2>
<p>SEO 最重要的方面是创建高质量的内容,使其能在网络上被广泛链接。但是,构建能够良好排名的网站,还需要考虑一些技术因素。</p>
<h3 id="开箱即用的功能">开箱即用的功能</h3>
<h4 id="ssr服务端渲染">SSR(服务端渲染)</h4>
<p>虽然近年来搜索引擎在索引客户端 JavaScript 渲染的内容方面有所改善,但服务端渲染的内容仍然能够更频繁和可靠地被索引。SvelteKit 默认采用 SSR,虽然您可以在<code>handle</code>中禁用它,但除非有充分的理由,否则应该保持启用。</p>
<blockquote>
<p>[!NOTE] SvelteKit 的渲染是高度可配置的,您可以根据需要实现动态渲染。但这通常不推荐,因为 SSR 除了 SEO 之外还有其他好处。</p>
</blockquote>
<h4 id="性能">性能</h4>
<p>核心网页指标等信号会影响搜索引擎排名。由于 Svelte 和 SvelteKit 引入的开销最小,因此更容易构建高性能网站。您可以使用 Google 的 PageSpeed Insights 或 Lighthouse 测试您的网站性能。详情请阅读性能页面。</p>
<h4 id="url-标准化">URL 标准化</h4>
<p>SvelteKit 会将带有尾部斜杠的路径重定向到不带斜杠的路径(或根据您的配置反向操作),因为重复的 URL 对 SEO 不利。</p>
<h3 id="手动设置">手动设置</h3>
<h4 id="title-和-meta"><code>&lt;title&gt;</code> 和 <code>&lt;meta&gt;</code></h4>
<p>每个页面都应该在 <code>&lt;svelte:head&gt;</code> 中包含精心编写的独特的 <code>&lt;title&gt;</code> 和 <code>&lt;meta name="description"&gt;</code> 元素。关于如何编写描述性标题和描述,以及其他使内容便于搜索引擎理解的建议,可以在Google的 Lighthouse SEO 审核 文档中找到。</p>
<blockquote>
<p>[!NOTE] 一个常见的模式是从页面的 <code>load</code> 函数返回与 SEO 相关的 <code>data</code>,然后在根布局的 <code>&lt;svelte:head&gt;</code> 中使用它(作为<code>page.data</code>)。</p>
</blockquote>
<h4 id="站点地图">站点地图</h4>
<p>站点地图可帮助搜索引擎对网站内的页面进行优先级排序,特别是当您有大量内容时。您可以使用端点动态创建站点地图:</p>
<pre><code class="language-js">/// file: src/routes/sitemap.xml/+server.js
export async function GET() {
        return new Response(
                `
                &lt;?xml version="1.0" encoding="UTF-8" ?&gt;
                &lt;urlset
                        xmlns="https://www.sitemaps.org/schemas/sitemap/0.9"
                        xmlns:xhtml="https://www.w3.org/1999/xhtml"
                        xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"
                        xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"
                        xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"
                        xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"
                &gt;
                        &lt;!-- &lt;url&gt;元素放在这里 --&gt;
                &lt;/urlset&gt;`.trim(),
                {
                        headers: {
                                'Content-Type': 'application/xml'
                        }
                }
        );
}
</code></pre>
<h4 id="amp">AMP</h4>
<p>现代网络开发的一个不幸现实是有时需要创建网站的加速移动页面(AMP)版本。在 SvelteKit 中,这可以通过设置 <code>inlineStyleThreshold</code> 选项来实现...</p>
<pre><code class="language-js">/// file: svelte.config.js
/** @type {import('@sveltejs/kit').Config} */
const config = {
        kit: {
                // 由于不允许使用&lt;link rel="stylesheet"&gt;,
                // 内联所有样式
                inlineStyleThreshold: Infinity
        }
};

export default config;
</code></pre>
<p>...在根 <code>+layout.js</code>/<code>+layout.server.js</code> 中禁用 <code>csr</code>...</p>
<pre><code class="language-js">/// file: src/routes/+layout.server.js
export const csr = false;
</code></pre>
<p>...在<code>app.html</code>中添加<code>amp</code>...</p>
<pre><code class="language-html">&lt;html amp&gt;
        ...
&lt;/html&gt;
</code></pre>
<p>...并使用从 <code>@sveltejs/amp</code> 导入的 <code>transform</code> 和 <code>transformPageChunk</code> 转换 HTML:</p>
<pre><code class="language-js">/// file: src/hooks.server.js
import * as amp from '@sveltejs/amp';

/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
        let buffer = '';
        return await resolve(event, {
                transformPageChunk: ({ html, done }) =&gt; {
                        buffer += html;
                        if (done) return amp.transform(buffer);
                }
        });
}
</code></pre>
<p>为了防止在将页面转换为 amp 时发送任何未使用的 CSS,我们可以使用<code>dropcss</code>:</p>
<pre><code class="language-js">// @filename: ambient.d.ts
declare module 'dropcss';

// @filename: index.js
// ---cut---
/// file: src/hooks.server.js
// @errors: 2307
import * as amp from '@sveltejs/amp';
import dropcss from 'dropcss';

/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
        let buffer = '';

        return await resolve(event, {
                transformPageChunk: ({ html, done }) =&gt; {
                        buffer += html;

                        if (done) {
                                let css = '';
                                const markup = amp
                                        .transform(buffer)
                                        .replace('⚡', 'amp') // dropcss无法处理此字符
                                        .replace(/&lt;style amp-custom([^&gt;]*?)&gt;([^]+?)&lt;\/style&gt;/, (match, attributes, contents) =&gt; {
                                                css = contents;
                                                return `&lt;style amp-custom${attributes}&gt;&lt;/style&gt;`;
                                        });

                                css = dropcss({ css, html: markup }).css;
                                return markup.replace('&lt;/style&gt;', `${css}&lt;/style&gt;`);
                        }
                }
        });
}
</code></pre>
<blockquote>
<p>[!NOTE] 使用 <code>handle</code> hook 通过 <code>amphtml-validator</code> 验证转换后的 HTML 是个好主意,但由于速度很慢,仅建议在预渲染页面时使用。</p>
</blockquote>
<h2 id="svelte-中文文档">Svelte 中文文档</h2>
<p>点击查看中文文档:</p>
<ol>
<li>SvelteKit 无障碍</li>
<li>SvelteKit SEO</li>
</ol>
<p>系统学习 Svelte,欢迎入手小册《Svelte 开发指南》。语法篇、实战篇、原理篇三大篇章带你系统掌握 Svelte!</p>
<p>此外我还写过 JavaScript 系列、TypeScript 系列、React 系列、Next.js 系列、冴羽答读者问等 14 个系列文章, 全系列文章目录:https://github.com/mqyqingfeng/Blog</p>
<p>欢迎围观我的“网页版朋友圈”、加入“冴羽·成长陪伴社群”,踏上“前端大佬成长之路”。</p><br><br>
来源:https://www.cnblogs.com/yayujs/p/18839087
頁: [1]
查看完整版本: SvelteKit 最新中文文档教程(22)—— 最佳实践之无障碍与 SEO