SEO
SEO 最重要的方面是创建高质量的内容,并从网络上的各个地方广泛链接到这些内容。但是,对于构建排名良好的网站,还有一些技术注意事项。
开箱即用
SSR
虽然近年来搜索引擎在索引使用客户端 JavaScript 渲染的内容方面有所改进,但服务器端渲染的内容索引频率更高且更可靠。SvelteKit 默认使用 SSR,虽然您可以在 handle 中禁用它,但除非您有充分的理由,否则应保持启用状态。
SvelteKit 的渲染高度可配置,您可以根据需要实现 动态渲染。通常不建议这样做,因为 SSR 除了 SEO 之外还有其他好处。
性能
诸如 核心 Web 指标 等信号会影响搜索引擎排名。由于 Svelte 和 SvelteKit 引入了最少的开销,因此更容易构建高性能的网站。您可以使用 Google 的 PageSpeed Insights 或 Lighthouse 测试您网站的性能。阅读 性能页面 以了解更多详细信息。
标准化 URL
SvelteKit 会将带有尾部斜杠的路径名重定向到没有尾部斜杠的路径名(反之亦然,具体取决于您的 配置),因为重复的 URL 对 SEO 不利。
手动设置
<title> 和 <meta>
每个页面都应该在 <svelte:head> 内具有编写良好且唯一的 <title> 和 <meta name="description"> 元素。有关如何编写描述性标题和描述以及其他关于使内容易于搜索引擎理解的建议,可以在 Google 的 Lighthouse SEO 审核 文档中找到。
一种常见模式是从页面
load函数返回与 SEO 相关的data,然后在根 布局 中的<svelte:head>中使用它(作为$page.data)。
站点地图
站点地图 可帮助搜索引擎优先考虑您网站中的页面,尤其是在您有大量内容时。您可以使用端点动态创建站点地图
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 选项来实现...
/** @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...
export const const csr: falsecsr = false;...将 amp 添加到您的 app.html 中
<html amp>
......并使用 transformPageChunk 以及从 @sveltejs/amp 导入的 transform 来转换 HTML
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<Partial<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<Partial<Record<string, string>>, string | null>event, {
ResolveOptions.transformPageChunk?(input: {
html: string;
done: boolean;
}): MaybePromise<string | 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
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<Partial<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<Partial<Record<string, string>>, string | null>event, {
ResolveOptions.transformPageChunk?(input: {
html: string;
done: boolean;
}): MaybePromise<string | 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<Partial<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<Partial<Record<string, string>>, string | null>event, {
ResolveOptions.transformPageChunk?(input: {
html: string;
done: boolean;
}): MaybePromise<string | 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,但前提是您正在预渲染页面,因为它非常慢。