SEO
SEOにおいて最も重要なことは、ウェブ全体から広くリンクされる高品質なコンテンツを作成することです。しかし、サイトのランキングを上げるための技術的な考慮事項もいくつかあります。
設定不要
SSR
近年、検索エンジンはクライアントサイドのJavaScriptでレンダリングされたコンテンツのインデックス作成能力が向上しましたが、サーバーサイドでレンダリングされたコンテンツの方がより頻繁かつ確実にインデックスされます。SvelteKitはデフォルトでSSRを採用しており、handle
で無効にすることはできますが、正当な理由がない限り有効にしておくべきです。
SvelteKitのレンダリングは高度に設定可能で、必要に応じて動的レンダリングを実装できます。一般的に、SSRにはSEO以外のメリットもあるため、あまり推奨されません。
パフォーマンス
Core Web Vitalsなどのシグナルは、検索エンジンのランキングに影響を与えます。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) => 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
現代のウェブ開発における残念な現実は、サイトの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;
...ルートの+layout.js
/ +layout.server.js
でcsr
を無効にし...
export const const csr: false
csr = false;
...amp
をapp.html
に追加し...
<html amp>
...
...@sveltejs/amp
からインポートしたtransform
とともにtransformPageChunk
を使用して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>`);
}
}
});
};
変換されたHTMLを
amphtml-validator
を使用して検証するためにhandle
フックを使用することをお勧めしますが、非常に遅いため、ページのプリレンダリングを行っている場合に限ります。