Page options
デフォルトでは、SvelteKit はどのコンポーネントも最初はサーバーでレンダリング (または プリレンダリング) し、それを HTML としてクライアントに送信します。その後、ブラウザ上でコンポーネントを再度レンダリングし、ハイドレーション(hydration)と呼ばれるプロセスでそれをインタラクティブなものにします。このため、コンポーネントが両方の場所で実行できることを確認する必要があります。SvelteKit はそれから ルーター(router) を初期化し、その後のナビゲーションを引き継ぎます。
これらはそれぞれオプションを +page.js
や +page.server.js
からエクスポートすることでページごとに、または共有の +layout.js
や +layout.server.js
を使用してページグループごとに制御することが可能です。アプリ全体に対してオプションを定義するには、最上位のレイアウト(root layout)からそれをエクスポートします。子レイアウトとページは親レイアウトで設定された値を上書きするため、例えば、プリレンダリングをアプリ全体で有効にし、それから動的にレンダリングする必要があるページではそれを無効にすることができます。
アプリの様々な領域でこれらのオプションをうまく組み合わせることができます。例えば、マーケティングページは高速化を最大限にするためにプリレンダリングし、動的なページは SEO とアクセシビリティのためにサーバーでレンダリングし、管理者用のセクションはクライアントのみでレンダリングするようにして SPA にすることができます。このように、SvelteKit はとても万能で多くの用途にお使いいただけます。
prerender
あなたのアプリの、少なくともいくつかのルートは、ビルド時に生成されるシンプルな HTML ファイルとして表現されることが多いでしょう。これらのルート(routes)を プリレンダリング することができます。
export const const prerender: true
prerender = true;
代わりに、export const prerender = true
を最上位(root)の +layout.js
または +layout.server.js
に設定し、明示的にプリレンダリングしないものとしてマークされたページを除き、全てをプリレンダリングできます:
export const const prerender: false
prerender = false;
prerender = true
があるルート(routes)は動的な SSR を行うのに使用する manifest から除外されるため、サーバー (または serverless/edge functions) を小さくすることができます。場合によっては、ルート(route)をプリレンダリングしつつ、manifest にも含めたいことがあるでしょう (例えば、/blog/[slug]
のようなルート(route)があり、最も新しい/人気のあるコンテンツはプリレンダリングしたいがめったにアクセスされないものはサーバーでレンダリングしたい、など)。こういったケースのために、3つ目のオプションがあります、’auto’ です:
export const const prerender: "auto"
prerender = 'auto';
もしアプリ全体がプリレンダリングに適している場合は、
adapter-static
を使うことで、任意の静的 Web サーバーで使用するのに適したファイルを出力することができます。
プリレンダラはアプリの最上位(root)から開始され、プリレンダリング可能なページや +server.js
ルート(routes)を見つけると、そのファイルを生成します。各ページは、プリレンダリングの候補である他のページを指し示す <a>
要素を見つけるためにスキャンされます。このため、通常はどのページにアクセスすべきか指定する必要はありません。もしプリレンダラがアクセスするページを指定する必要がある場合は、config.kit.prerender.entries
で指定するか、動的なルート(route)から entries
関数をエクスポートします。
プリレンダリング中、$app/environment
からインポートされる building
の値は true
になります。
Prerendering server routes
他のページオプションとは違い、prerender
は +server.js
ファイルにも適用できます。これらのファイルはレイアウトから影響を受けませんが、そこからデータを読み込むページからデフォルトの値を継承します。例えば、+page.js
がこの load
関数を含む場合…
export const const prerender: true
prerender = true;
/** @type {import('./$types').PageLoad} */
export async function function load({ fetch }: {
fetch: any;
}): Promise<any>
load({ fetch: any
fetch }) {
const const res: any
res = await fetch: any
fetch('/my-server-route.json');
return await const res: any
res.json();
}
import type { type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad } from './$types';
export const const prerender: true
prerender = true;
export const const load: PageLoad
load: type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
type PageLoad = (event: Kit.LoadEvent<Record<string, any>, Record<string, any> | null, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>
PageLoad = async ({ fetch: {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response>;
}
fetch
is equivalent to the native fetch
web API, with a few additional features:
- It can be used to make credentialed requests on the server, as it inherits the
cookie
and authorization
headers for the page request.
- It can make relative requests on the server (ordinarily,
fetch
requires a URL with an origin when used in a server context).
- Internal requests (e.g. for
+server.js
routes) go directly to the handler function when running on the server, without the overhead of an HTTP call.
- During server-side rendering, the response will be captured and inlined into the rendered HTML by hooking into the
text
and json
methods of the Response
object. Note that headers will not be serialized, unless explicitly included via filterSerializedResponseHeaders
- During hydration, the response will be read from the HTML, guaranteeing consistency and preventing an additional network request.
You can learn more about making credentialed requests with cookies here
fetch }) => {
const const res: Response
res = await fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response> (+1 overload)
fetch('/my-server-route.json');
return await const res: Response
res.Body.json(): Promise<any>
json();
};
…それから src/routes/my-server-route.json/+server.js
は、自身の export const prerender = false
を含んでいなければ、プリレンダリング可能であると扱われることになります。
プリレンダリングしない場合
基本的なルールは次の通りです: ページがプリレンダリング可能であると言うためには、そのページを直接表示する2人のユーザーが、サーバーから同じコンテンツを取得できなけれなりません。
全てのページがプリレンダリングに適しているわけではありません。プリレンダリングされたコンテンツは全てのユーザーに表示されます。もちろん、プリレンダリングされたページの
onMount
でパーソナライズされたデータをフェッチできますが、ブランクの初期コンテンツやローディングインジケーターにより、ユーザエクスペリエンスが低下してしまう可能性があります。
src/routes/blog/[slug]/+page.svelte
ルート(route)のような、ページのパラメータを元にデータをロードするページもプリレンダリングができることにご注意ください。
プリレンダリング中に url.searchParams
にアクセスすることは禁止されています。もし使う必要があるなら、ブラウザの中だけで行うようにしてください (例えば onMount
の中で)。
action 付きのページは、サーバーがその action の POST
リクエストを処理できなければならないため、プリレンダリングできません。
ルートの衝突(Route conflicts)
プリレンダリングはファイルシステムに書き込むため、ディレクトリとファイルが同じ名前になるエンドポイントを2つ持つことはできません。例えば、src/routes/foo/+server.js
と src/routes/foo/bar/+server.js
の場合は、foo
と foo/bar
を作成しようとしますが、これは不可能です。
このため(他にも理由はありますが)、常に拡張子を付けておくことを推奨します — src/routes/foo.json/+server.js
と src/routes/foo/bar.json/+server.js
は、foo.json
と foo/bar.json
ファイルが並んで調和して共存できます。
ページの場合は、foo
ではなく foo/index.html
を書き込むことでこの問題を回避しています。
Troubleshooting
‘The following routes were marked as prerenderable, but were not prerendered’ というようなエラーが表示されたら、それは該当のルート (またはページの場合は親レイアウト) に export const prerender = true
があるにもかかわらず、プリレンダリングクローラーがそのページにアクセスせず、そのページがプリレンダリングされていないことが原因です。
これらのルート(route)は動的にサーバーレンダリングできないため、該当のルート(route)にアクセスしようとしたときにエラーが発生します。それを解決するには、いくつか方法があります:
- SvelteKit が
config.kit.prerender.entries
かentries
ページオプションからのリンクを辿ってそのルート(route)を見つけられるようにしてください。動的なルート(例えば[parameters]
を持つページ) へのリンクは、他のエントリーポイントをクローリングしても見つからない場合はこのオプションに追加してください。そうしないと、SvelteKit はその parameters が持つべき値がわからないので、プリレンダリングされません。プリレンダリング可能(prerenderable)なページとしてマークされていないページは無視され、そのページから他のページ(プリレンダリング可能なものも含む)へのリンクもクローリングされません。 - サーバーサイドレンダリングが可能な別のプリレンダリングページで、該当のルートのリンクを検出できるようにしてください。
export const prerender = true
からexport const prerender = 'auto'
に変更してください。'auto'
になっているルート(route)は動的にサーバーレンダリングすることができます
entries
SvelteKit は、 エントリーポイント(entry points) を開始地点としてクローリングを行うことでページを自動的に発見します。デフォルトでは、動的でないルート(route)はすべてエントリーポイントとみなされます。例えば、以下のルートがある場合…
/ # non-dynamic
/blog# non-dynamic
/blog/[slug] # dynamic, because of `[slug]`
…SvelteKit は /
と /blog
をプリレンダリングし、その過程で <a href="/blog/hello-world">
などのリンクを発見し、それをプリレンダリング対象とします。
ほとんどの場合、これで十分です。しかし状況によっては、/blog/hello-world
などのページに対するリンクが存在しない (あるいはプリレンダリングされたページには存在しない) 場合があります。この場合、SvelteKit にその存在を知らせる必要があります。
これを行うには config.kit.prerender.entries
で指定するか、動的なルート(route) に属する +page.js
か +page.server.js
か +server.js
で entries
関数をエクスポートします:
/** @type {import('./$types').EntryGenerator} */
export function function entries(): {
slug: string;
}[]
entries() {
return [
{ slug: string
slug: 'hello-world' },
{ slug: string
slug: 'another-blog-post' }
];
}
export const const prerender: true
prerender = true;
import type { type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
EntryGenerator } from './$types';
export const const entries: EntryGenerator
entries: type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
type EntryGenerator = () => Promise<Array<Record<string, any>>> | Array<Record<string, any>>
EntryGenerator = () => {
return [
{ slug: string
slug: 'hello-world' },
{ slug: string
slug: 'another-blog-post' }
];
};
export const const prerender: true
prerender = true;
entries
は async
関数にすることができるので、(例えば) 上記で示したように CMS や データベースから投稿リストを取得することもできます。
ssr
通常、SvelteKit ではページを最初にサーバーでレンダリングし、その HTML をクライアントに送信してハイドレーションを行います。もし ssr
を false
に設定した場合、代わりに空の ‘shell’ ページがレンダリングされます。これはページがサーバーでレンダリングできない場合には便利 (例えば document
などのブラウザオンリーな globals を使用するなど) ですが、ほとんどの状況では推奨されません (appendix をご参照ください)。
export const const ssr: false
ssr = false;
// If both `ssr` and `csr` are `false`, nothing will be rendered!
export const ssr = false
を最上位(root)の +layout.js
に追加した場合、アプリ全体がクライアントのみでレンダリングされるようになり、それはつまり、本質的にはアプリを SPA にする、ということを意味します。
csr
通常、SvelteKit はサーバーでレンダリングされた HTML を、クライアントサイドレンダリング(CSR)されたインタラクティブなページに ハイドレーション します。JavaScript を全く必要としないページもあります。多くのブログ記事や ‘about’ ページがこのカテゴリに入ります。このような場合は CSR を無効にすることができます:
export const const csr: false
csr = false;
// If both `csr` and `ssr` are `false`, nothing will be rendered!
CSR を無効にすると、クライアントに JavaScript が送信されません。つまり:
- web ページは HTML と CSS だけで動作します。
- すべての Svelte コンポーネントの
<script>
タグは削除されます。 <form>
要素をプログレッシブ・エンハンスメントにすることはできません。- リンクはブラウザによってフルページナビゲーションで処理されます。
- Hot Module Replacement (HMR) が無効になります。
(例えば HMR を活用したい場合に) 開発時に csr
を有効にするには以下のようにします:
import { const dev: boolean
Whether the dev server is running. This is not guaranteed to correspond to NODE_ENV
or MODE
.
dev } from '$app/environment';
export const const csr: boolean
csr = const dev: boolean
Whether the dev server is running. This is not guaranteed to correspond to NODE_ENV
or MODE
.
dev;
trailingSlash
デフォルトでは、SvelteKit は URL から末尾のスラッシュ(trailing slash)を取り除きます。/about/
にアクセスすると、/about
へのリダイレクトをレスポンスとして受け取ることになります。この動作は、trailingSlash
オプションで変更することができます。指定できる値は 'never'
(デフォルト)、'always'
、'ignore'
です。
他のページオプションと同様に、+layout.js
や +layout.server.js
からこの値をエクスポートすると、すべての子のページに適用されます。+server.js
ファイルからその設定をエクスポートすることもできます。
export const const trailingSlash: "always"
trailingSlash = 'always';
このオプションは プリレンダリング にも影響します。trailingSlash
が always
の場合 /about
というルート(route)は about/index.html
ファイルとなり、それ以外の場合は about.html
が作成され、静的な Web サーバの慣習を反映したものになります。
末尾のスラッシュを無視することは推奨されません。相対パスのセマンティクスが2つのケースで異なり(
/x
からの./y
は/y
ですが、/x/
からは/x/y
となります)、/x
と/x/
は別の URL として扱われ、SEO 上有害となるからです。
config
adapter のコンセプトにより、SvelteKit は様々なプラットフォーム上で実行することができます。しかし、各プラットフォームには、デプロイメントをさらに微調整するための特定の設定があるかもしれません — 例えば Vercel では、アプリのある部分はエッジに、他の部分はサーバーレス環境にデプロイするのを選択することができます。
config
はトップレベルで key-value ペアを持つオブジェクトです。その他の具体的な形は、使用する adapter に依存します。すべての adapter は型安全性のためにインポート可能な Config
インターフェースを提供することになっています。詳細な情報については、使用する adapter のドキュメントを参照してください。
/** @type {import('some-adapter').Config} */
export const const config: Config
config = {
Config.runtime: string
runtime: 'edge'
};
import type { Config } from 'some-adapter';
export const const config: Config
config: Config = {
Config.runtime: string
runtime: 'edge'
};
config
オブジェクトはトップレベル(top level)でマージされます (より深いレベル(deeper levels)ではマージされません)。つまり、より上位の +layout.js
にある値の一部を上書きしたい場合に、+page.js
にある全ての値を繰り返す必要はないということです。例えばこの layout の設定は…
export const const config: {
runtime: string;
regions: string;
foo: {
bar: boolean;
};
}
config = {
runtime: string
runtime: 'edge',
regions: string
regions: 'all',
foo: {
bar: boolean;
}
foo: {
bar: boolean
bar: true
}
}
…この page の設定で上書きされ…
export const const config: {
regions: string[];
foo: {
baz: boolean;
};
}
config = {
regions: string[]
regions: ['us1', 'us2'],
foo: {
baz: boolean;
}
foo: {
baz: boolean
baz: true
}
}
…このページの設定の値は { runtime: 'edge', regions: ['us1', 'us2'], foo: { baz: true } }
となります。