SEO
SEO で最も重要なのは、高品質なコンテンツを作ること、そしてそれが web 上で広くリンクされることです。しかし、ランクが高いサイトを構築するためにいくつか技術的に考慮すべきこともあります。
Out of the box
SSR
近年、検索エンジンはクライアントサイドの JavaScript でレンダリングされたコンテンツのインデックスを改善してきましたが、サーバーサイドレンダリングされたコンテンツのほうがより頻繁に、より確実にインデックスされます。SvelteKit はデフォルトで SSR を採用しています。handle
で無効にすることもできますが、適切な理由がない場合はそのままにしておきましょう。
SvelteKit のレンダリングは高度な設定が可能です。必要であれば、動的なレンダリング(dynamic rendering) を実装することも可能です。一般的には推奨されません、SSR には SEO 以外のメリットもあるからです。
パフォーマンス
Core Web Vitals のような指標は検索エンジンのランクに影響を与えます。Svelte と SvelteKit はオーバーヘッドが最小限であるため、ハイパフォーマンスなサイトを簡単に構築できです。Google の PageSpeed Insights や Lighthouse で、ご自身のサイトをテストすることができます。詳細は パフォーマンスのページ をお読みください。
URLの正規化
SvelteKit は、末尾のスラッシュ(trailing slash)付きのパス名から、末尾のスラッシュが無いパス名にリダイレクトします (設定 で逆にできます)。URLの重複は、SEOに悪影響を与えます。
Manual setup
<title> と <meta>
全てのページで、よく練られたユニークな <title>
と <meta name="description">
を <svelte:head>
の内側に置くべきです。説明的な title と description の書き方に関するガイダンスと、検索エンジンにとってわかりやすいコンテンツを作るためのその他の方法については、Google の Lighthouse SEO audits のドキュメントで見つけることができます。
よくあるパターンとしては、ページの
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) => Response
This 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(): string
Removes the leading and trailing white space and line terminator characters from a string.
trim(),
{
ResponseInit.headers?: HeadersInit | undefined
headers: {
'Content-Type': 'application/xml'
}
}
);
}
AMP
現代の web 開発における不幸な現実として、サイトの Accelerated Mobile Pages (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: number
inlineStyleThreshold: var Infinity: number
Infinity
}
};
export default const config: {
kit: {
inlineStyleThreshold: number;
};
}
config;
…最上位(root)の +layout.js
/ +layout.server.js
の csr
を無効にします…
export const const csr: false
csr = false;
…amp
を app.html
に追加します
<html amp>
...
…そして、transformPageChunk
と、@sveltejs/amp
からインポートできる transform
を使用して、HTML を変換します:
import * as import amp
amp from '@sveltejs/amp';
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle({ event, resolve }: {
event: any;
resolve: any;
}): Promise<any>
handle({ event: any
event, resolve: any
resolve }) {
let let buffer: string
buffer = '';
return await resolve: any
resolve(event: any
event, {
transformPageChunk: ({ html, done }: {
html: any;
done: any;
}) => string | undefined
transformPageChunk: ({ html: any
html, done: any
done }) => {
let buffer: string
buffer += html: any
html;
if (done: any
done) return import amp
amp.function transform(html: string): string
transform(let buffer: string
buffer);
}
});
}
import * as import amp
amp 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: Handle
handle: 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: string
buffer = '';
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: string
html, done: boolean
done }) => {
let buffer: string
buffer += html: string
html;
if (done: boolean
done) return import amp
amp.function transform(html: string): string
transform(let buffer: string
buffer);
}
});
};
ページを amp に変換した結果として未使用の CSS が配布されてしまうのを防ぎたければ、dropcss
を使用すると良いでしょう:
import * as import amp
amp 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: string
buffer = '';
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: string
html, done: boolean
done }) => {
let buffer: string
buffer += html: string
html;
if (done: boolean
done) {
let let css: string
css = '';
const const markup: string
markup = import amp
amp
.function transform(html: string): string
transform(let buffer: string
buffer)
.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: string
match, attributes: any
attributes, contents: any
contents) => {
let css: string
css = contents: any
contents;
return `<style amp-custom${attributes: any
attributes}></style>`;
});
let css: string
css = module "dropcss"
dropcss({ css: string
css, html: string
html: const markup: string
markup }).css;
return const markup: string
markup.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: string
css}</style>`);
}
}
});
}
import * as import amp
amp 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: Handle
handle: 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: string
buffer = '';
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: string
html, done: boolean
done }) => {
let buffer: string
buffer += html: string
html;
if (done: boolean
done) {
let let css: string
css = '';
const const markup: string
markup = import amp
amp
.function transform(html: string): string
transform(let buffer: string
buffer)
.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: string
match, attributes: any
attributes, contents: any
contents) => {
let css: string
css = contents: any
contents;
return `<style amp-custom${attributes: any
attributes}></style>`;
});
let css: string
css = module "dropcss"
dropcss({ css: string
css, html: string
html: const markup: string
markup }).css;
return const markup: string
markup.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: string
css}</style>`);
}
}
});
};
amphtml-validator
を使用して変換された HTML を検証するのに、handle
hook を利用するのは良いアイデアですが、非常に遅くなってしまうので、ページをプリレンダリングするときだけにしてください。