Skip to main content

SEO

SEO で最も重要なのは、高品質なコンテンツを作ること、そしてそれが web 上で広くリンクされることです。しかし、ランクが高いサイトを構築するためにいくつか技術的に考慮すべきこともあります。

Out of the box

SSR

近年、検索エンジンはクライアントサイドの JavaScript でレンダリングされたコンテンツのインデックスを改善してきましたが、サーバーサイドレンダリングされたコンテンツのほうがより頻繁に、より確実にインデックスされます。SvelteKit はデフォルトで SSR を採用しています。handle で無効にすることもできますが、適切な理由がない場合はそのままにしておきましょう。

SvelteKit のレンダリングは高度な設定が可能です。必要であれば、動的なレンダリング(dynamic rendering) を実装することも可能です。一般的には推奨されません、SSR には SEO 以外のメリットもあるからです。

パフォーマンス

Core Web Vitals のような指標は検索エンジンのランクに影響を与えます。Svelte と SvelteKit はオーバーヘッドが最小限であるため、ハイパフォーマンスなサイトを簡単に構築できです。Google の PageSpeed InsightsLighthouse で、ご自身のサイトをテストすることができます。詳細は パフォーマンスのページ をお読みください。

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 として) 使用することです。

サイトマップ

サイトマップ は、検索エンジンがサイト内のページの優先順位付けをするのに役立ちます、特にコンテンツの量が多い場合は。エンドポイントを使用してサイトマップを動的に作成できます:

src/routes/sitemap.xml/+server
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.

MDN Reference

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 | undefinedheaders: { 'Content-Type': 'application/xml' } } ); }

AMP

現代の web 開発における不幸な現実として、サイトの Accelerated Mobile Pages (AMP) バージョンを作らなければならないときがある、というのがあります。SvelteKit では、inlineStyleThreshold オプションを設定することでこれを実現することができます…

svelte.config
/** @type {import('@sveltejs/kit').Config} */
const 
const config: {
    kit: {
        inlineStyleThreshold: number;
    };
}
@type{import('@sveltejs/kit').Config}
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;
    };
}
@type{import('@sveltejs/kit').Config}
config
;

…最上位(root)の +layout.js / +layout.server.jscsr を無効にします…

src/routes/+layout.server
export const const csr: falsecsr = false;

ampapp.html に追加します

<html amp>
...

…そして、transformPageChunk と、@sveltejs/amp からインポートできる transform を使用して、HTML を変換します:

src/hooks.server
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>
@type{import('@sveltejs/kit').Handle}
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.

@paraminput the html chunk and the info if this is the last chunk
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 を使用すると良いでしょう:

src/hooks.server
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<...>
@type{import('@sveltejs/kit').Handle}
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.

@paraminput the html chunk and the info if this is the last chunk
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.

@paramsearchValue A string or regular expression to search for.
@paramreplaceValue A string containing the text to replace. When the {@linkcode searchValue} is a RegExp, all matches are replaced if the g flag is set (or only those matches at the beginning, if the y flag is also present). Otherwise, only the first match of {@linkcode searchValue} is replaced.
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.

@paramsearchValue A object can search for and replace matches within a string.
@paramreplacer A function that returns the replacement text.
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.

@paramsearchValue A string or regular expression to search for.
@paramreplaceValue A string containing the text to replace. When the {@linkcode searchValue} is a RegExp, all matches are replaced if the g flag is set (or only those matches at the beginning, if the y flag is also present). Otherwise, only the first match of {@linkcode searchValue} is replaced.
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.

@paraminput the html chunk and the info if this is the last chunk
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.

@paramsearchValue A string or regular expression to search for.
@paramreplaceValue A string containing the text to replace. When the {@linkcode searchValue} is a RegExp, all matches are replaced if the g flag is set (or only those matches at the beginning, if the y flag is also present). Otherwise, only the first match of {@linkcode searchValue} is replaced.
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.

@paramsearchValue A object can search for and replace matches within a string.
@paramreplacer A function that returns the replacement text.
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.

@paramsearchValue A string or regular expression to search for.
@paramreplaceValue A string containing the text to replace. When the {@linkcode searchValue} is a RegExp, all matches are replaced if the g flag is set (or only those matches at the beginning, if the y flag is also present). Otherwise, only the first match of {@linkcode searchValue} is replaced.
replace
('</style>', `${let css: stringcss}</style>`);
} } }); };

amphtml-validator を使用して変換された HTML を検証するのに、handle hook を利用するのは良いアイデアですが、非常に遅くなってしまうので、ページをプリレンダリングするときだけにしてください。

Edit this page on GitHub