SEO
SEO 最重要的方面是创建来自网络的广泛链接的高质量内容。但是,构建排名靠前的网站需要考虑一些技术问题。
¥The most important aspect of SEO is to create high-quality content that is widely linked to from around the web. However, there are a few technical considerations for building sites that rank well.
开箱即用(Out of the box)
¥Out of the box
SSR
虽然近年来搜索引擎在索引使用客户端 JavaScript 渲染的内容方面有所改进,但服务器端渲染的内容被索引的频率和可靠性更高。SvelteKit 默认使用 SSR,虽然你可以在 handle 中禁用它,但除非你有充分的理由不这样做,否则应该将其保留。
¥While search engines have got better in recent years at indexing content that was rendered with client-side JavaScript, server-side rendered content is indexed more frequently and reliably. SvelteKit employs SSR by default, and while you can disable it in handle, you should leave it on unless you have a good reason not to.
SvelteKit 的渲染高度可配置,你可以根据需要实现 dynamic rendering。通常不建议这样做,因为服务器端渲染 (SSR) 除了 SEO 之外还有其他好处。
性能(Performance)
¥Performance
诸如 核心 Web 要素 之类的信号会影响搜索引擎排名。由于 Svelte 和 SvelteKit 的开销极小,因此它们使构建高性能网站变得更加容易。你可以使用 Google 的 PageSpeed Insights 或 Lighthouse 测试你网站的性能。只需几个关键操作,例如使用 SvelteKit 的默认 混合渲染 模式和 优化图片,即可显著提升网站速度。阅读 性能页面 了解更多详情。
¥Signals such as Core Web Vitals impact search engine ranking. Because Svelte and SvelteKit introduce minimal overhead, they make it easier to build high performance sites. You can test your site’s performance using Google’s PageSpeed Insights or Lighthouse. With just a few key actions like using SvelteKit’s default hybrid rendering mode and optimizing your images, you can greatly improve your site’s speed. Read the performance page for more details.
规范化 URL(Normalized URLs)
¥Normalized URLs
SvelteKit 将带有尾部斜杠的路径名重定向为不带斜杠的路径名(反之亦然,具体取决于你的 configuration),因为重复的 URL 对 SEO 不利。
¥SvelteKit redirects pathnames with trailing slashes to ones without (or vice versa depending on your configuration), as duplicate URLs are bad for SEO.
手动设置(Manual setup)
¥Manual setup
<title> 和 <meta>(<title> and <meta>)
¥<title> and <meta>
每个页面都应该在 <svelte:head> 内有编写良好且独特的 <title> 和 <meta name="description"> 元素。有关如何编写描述性标题和说明的指南,以及使搜索引擎可以理解内容的其他建议,可在 Google 的 Lighthouse SEO 审核 文档中找到。
¥Every page should have well-written and unique <title> and <meta name="description"> elements inside a <svelte:head>. Guidance on how to write descriptive titles and descriptions, along with other suggestions on making content understandable by search engines, can be found on Google’s Lighthouse SEO audits documentation.
一种常见的模式是从页面
load函数返回与 SEO 相关的data,然后在根 layout 中的<svelte:head>中使用它(作为page.data)。
站点地图(Sitemaps)
¥Sitemaps
站点地图 帮助搜索引擎优先考虑你网站内的页面,特别是当你有大量内容时。你可以使用端点动态创建站点地图:
¥Sitemaps help search engines prioritize pages within your site, particularly when you have a large amount of content. You can create a sitemap dynamically using an endpoint:
export async function function GET(): Promise<Response>GET() {
return new var Response: new (body?: BodyInit | null, init?: ResponseInit) => ResponseThis Fetch API interface represents the response to a request.
Response(
`
<?xml version="1.0" encoding="UTF-8" ?>
<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"
>
<!-- <url> elements go here -->
</urlset>`.String.trim(): stringRemoves the leading and trailing white space and line terminator characters from a string.
trim(),
{
ResponseInit.headers?: HeadersInit | undefinedheaders: {
'Content-Type': 'application/xml'
}
}
);
}AMP
现代 Web 开发的一个不幸现实是,有时需要创建网站的 加速移动页面 (AMP) 版本。在 SvelteKit 中,可以通过设置 inlineStyleThreshold 选项来实现这一点……
¥An unfortunate reality of modern web development is that it is sometimes necessary to create an Accelerated Mobile Pages (AMP) version of your site. In SvelteKit this can be done by setting the inlineStyleThreshold option...
/** @type {import('@sveltejs/kit').Config} */
const const config: {
kit: {
inlineStyleThreshold: number;
};
}
config = {
kit: {
inlineStyleThreshold: number;
}
kit: {
// since <link rel="stylesheet"> isn't
// allowed, inline all styles
inlineStyleThreshold: numberinlineStyleThreshold: var Infinity: numberInfinity
}
};
export default const config: {
kit: {
inlineStyleThreshold: number;
};
}
config;...在根 +layout.js / +layout.server.js 中禁用 csr...
¥...disabling csr in your root +layout.js / +layout.server.js...
export const const csr: falsecsr = false;...将 amp 添加到你的 app.html
¥...adding amp to your app.html
<html amp>
......并使用 transformPageChunk 以及从 @sveltejs/amp 导入的 transform 转换 HTML:
¥...and transforming the HTML using transformPageChunk along with transform imported from @sveltejs/amp:
import * as import ampamp from '@sveltejs/amp';
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle({ event, resolve }: {
event: any;
resolve: any;
}): Promise<any>
handle({ event: anyevent, resolve: anyresolve }) {
let let buffer: stringbuffer = '';
return await resolve: anyresolve(event: anyevent, {
transformPageChunk: ({ html, done }: {
html: any;
done: any;
}) => string | undefined
transformPageChunk: ({ html: anyhtml, done: anydone }) => {
let buffer: stringbuffer += html: anyhtml;
if (done: anydone) return import ampamp.function transform(html: string): stringtransform(let buffer: stringbuffer);
}
});
}import * as import ampamp from '@sveltejs/amp';
import type { type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle } from '@sveltejs/kit';
export const const handle: Handlehandle: type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle = async ({ event: RequestEvent<Record<string, string>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) => {
let let buffer: stringbuffer = '';
return await resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Record<string, string>, string | null>event, {
ResolveOptions.transformPageChunk?: ((input: {
html: string;
done: boolean;
}) => MaybePromise<string | undefined>) | undefined
Applies custom transforms to HTML. If done is true, it’s the final chunk. Chunks are not guaranteed to be well-formed HTML
(they could include an element’s opening tag but not its closing tag, for example)
but they will always be split at sensible boundaries such as %sveltekit.head% or layout/page components.
transformPageChunk: ({ html: stringhtml, done: booleandone }) => {
let buffer: stringbuffer += html: stringhtml;
if (done: booleandone) return import ampamp.function transform(html: string): stringtransform(let buffer: stringbuffer);
}
});
};为了防止将页面转换为 amp 后发送任何未使用的 CSS,我们可以使用 dropcss:
¥To prevent shipping any unused CSS as a result of transforming the page to amp, we can use dropcss:
import * as import ampamp from '@sveltejs/amp';
import module "dropcss"dropcss from 'dropcss';
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle(input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}): MaybePromise<...>
handle({ event: RequestEvent<Record<string, string>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) {
let let buffer: stringbuffer = '';
return await resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Record<string, string>, string | null>event, {
ResolveOptions.transformPageChunk?: ((input: {
html: string;
done: boolean;
}) => MaybePromise<string | undefined>) | undefined
Applies custom transforms to HTML. If done is true, it’s the final chunk. Chunks are not guaranteed to be well-formed HTML
(they could include an element’s opening tag but not its closing tag, for example)
but they will always be split at sensible boundaries such as %sveltekit.head% or layout/page components.
transformPageChunk: ({ html: stringhtml, done: booleandone }) => {
let buffer: stringbuffer += html: stringhtml;
if (done: booleandone) {
let let css: stringcss = '';
const const markup: stringmarkup = import ampamp
.function transform(html: string): stringtransform(let buffer: stringbuffer)
.String.replace(searchValue: string | RegExp, replaceValue: string): string (+3 overloads)Replaces text in a string, using a regular expression or search string.
replace('⚡', 'amp') // dropcss can't handle this character
.String.replace(searchValue: {
[Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string;
}, replacer: (substring: string, ...args: any[]) => string): string (+3 overloads)
Replaces text in a string, using an object that supports replacement within a string.
replace(/<style amp-custom([^>]*?)>([^]+?)<\/style>/, (match: stringmatch, attributes: anyattributes, contents: anycontents) => {
let css: stringcss = contents: anycontents;
return `<style amp-custom${attributes: anyattributes}></style>`;
});
let css: stringcss = module "dropcss"dropcss({ css: stringcss, html: stringhtml: const markup: stringmarkup }).css;
return const markup: stringmarkup.String.replace(searchValue: string | RegExp, replaceValue: string): string (+3 overloads)Replaces text in a string, using a regular expression or search string.
replace('</style>', `${let css: stringcss}</style>`);
}
}
});
}
import * as import ampamp from '@sveltejs/amp';
import module "dropcss"dropcss from 'dropcss';
import type { type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle } from '@sveltejs/kit';
export const const handle: Handlehandle: type Handle = (input: {
event: RequestEvent;
resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle = async ({ event: RequestEvent<Record<string, string>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) => {
let let buffer: stringbuffer = '';
return await resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Record<string, string>, string | null>event, {
ResolveOptions.transformPageChunk?: ((input: {
html: string;
done: boolean;
}) => MaybePromise<string | undefined>) | undefined
Applies custom transforms to HTML. If done is true, it’s the final chunk. Chunks are not guaranteed to be well-formed HTML
(they could include an element’s opening tag but not its closing tag, for example)
but they will always be split at sensible boundaries such as %sveltekit.head% or layout/page components.
transformPageChunk: ({ html: stringhtml, done: booleandone }) => {
let buffer: stringbuffer += html: stringhtml;
if (done: booleandone) {
let let css: stringcss = '';
const const markup: stringmarkup = import ampamp
.function transform(html: string): stringtransform(let buffer: stringbuffer)
.String.replace(searchValue: string | RegExp, replaceValue: string): string (+3 overloads)Replaces text in a string, using a regular expression or search string.
replace('⚡', 'amp') // dropcss can't handle this character
.String.replace(searchValue: {
[Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string;
}, replacer: (substring: string, ...args: any[]) => string): string (+3 overloads)
Replaces text in a string, using an object that supports replacement within a string.
replace(/<style amp-custom([^>]*?)>([^]+?)<\/style>/, (match: stringmatch, attributes: anyattributes, contents: anycontents) => {
let css: stringcss = contents: anycontents;
return `<style amp-custom${attributes: anyattributes}></style>`;
});
let css: stringcss = module "dropcss"dropcss({ css: stringcss, html: stringhtml: const markup: stringmarkup }).css;
return const markup: stringmarkup.String.replace(searchValue: string | RegExp, replaceValue: string): string (+3 overloads)Replaces text in a string, using a regular expression or search string.
replace('</style>', `${let css: stringcss}</style>`);
}
}
});
};使用
handle钩子来验证使用amphtml-validator转换后的 HTML 是个好主意,但仅限于预渲染页面,因为预渲染速度非常慢。