🦛
SvelteKitのSSGで多言語対応(多言語ルーティング)したメモ
SvelteKitでSSGを使用しているのですが、Optional parametersを使用して多言語対応させようとしたところnpm run dev
(SSR相当と思われる)では動作するのにnpm run preview
で動作せず微妙に四苦八苦しました。備忘メモとしてその対応を残しておきます。
[@sveltejs/kit@2.16.1, @sveltejs/adapter-static@3.0.8]
結論
- ディレクトリ構成例
$ tree ./src/routes
./src/routes
├── [[lang]]
│ ├── hello
│ │ ├── +page.svelte
│ │ └── world
│ │ └── +page.svelte
│ └── +page.svelte
└── +layout.server.ts
- 設定内容
+layout.server.ts
export const prerender = true;
export const ssr = true;
export const trailingSlash = "always";
svelte.config.js
...
const config = {
...
kit: {
adapter: adapter({
...
}),
prerender: {
entries: ["*", "/en", "/ja"]
}
}
};
export default config;
- 得られる成果物 (表示順は変更加工済)
./build/
$ tree ./build
./build
├── _app
│ ├── ...
│ ...
├── __data.json
├── index.html
├── hello
│ ├── __data.json
│ ├── index.html
│ └── world
│ ├── __data.json
│ └── index.html
├── en
│ ├── __data.json
│ ├── hello
│ │ ├── __data.json
│ │ ├── index.html
│ │ └── world
│ │ ├── __data.json
│ │ └── index.html
│ └── index.html
└── ja
├── __data.json
├── hello
│ ├── __data.json
│ ├── index.html
│ └── world
│ ├── __data.json
│ └── index.html
└── index.html
結論設定内容と追加的な対応
設定内容
- 言語指定のない場合もページを表示したかったためOptional parametersを使用
- 必ず
/en
や/ja
を指定するようにする場合は[lang]
と指定する-
lang
は変数名のため何でも可
-
- 必ず
- SSGでは何も指定しないとDir/File出力時に
[[lang]]
部分に何が入るか不明になる- そのため許容する値を
svelte.config.js
のprerender.entries
で指定する -
svelte.config.js
ではなく代わりに以下ファイルでも指定可能src/routes/[[lang]]/+page.server.tsimport type { EntryGenerator } from "./$types"; export const entries: EntryGenerator = () => { return [ { lang: "ja" }, { lang: "en" }, ]; };
- そのため許容する値を
- ビルド時にどのようなディレクトリ構成を出力すべきか走査される
- その走査処理のことをSvelteKitでは"crawl"と呼んでいる
- crawlはSSR機能が有効の場合のみ機能する
- 最低限のcrawlはSSR無効でも動作するように見える
- 今回のようなOptional parametersがある場合は有効にする必要がある
- そのため
routes/+layout.server.ts
にexport const ssr = true;
を追加 - crawlにより
href
の指定等を辿ってその先のページも含まれるようになる-
prerender.entries
で走査起点を与えているという側面もある -
href
等で辿れない場合はprerender.entries
に含む必要がある
-
- それでもうまくいかないため追加で
trailingSlash = "always"
に設定する- デフォルトでは
trailingSlash = "never"
になっている -
never
でなぜうまくいかないのかは不明
- デフォルトでは
- 上記
ssr
とtrailingSlash
を異なる設定値にした場合の出力はSSG構成にならない- 具体的には言語別ディレクトリの中に
hello
以下の構造が出力されない
- 具体的には言語別ディレクトリの中に
各設定値でのビルド出力内容
-
ssr = true
,trailingSlash = "always"
-> 結論参照 -
ssr = true
,trailingSlash = "never"
dir/file tree
$ tree ./build
./build
├── _app
│ ├── ...
│ ...
├── __data.json
├── index.html
├── en.html
├── ja.html
├── hello.html
├── world.html
├── hello
│ ├── __data.json
│ ├── world
│ │ └── __data.json
│ └── world.html
├── en
│ └── __data.json
├── ja
│ └── __data.json
└── world
└── __data.json
-
ssr = false
,trailingSlash = "always"
dir/file tree
$ tree ./build
./build
├── _app
│ ├── ...
│ ...
├── __data.json
├── index.html
├── hello
│ ├── __data.json
│ ├── index.html
│ └── world
│ ├── __data.json
│ └── index.html
├── en
│ ├── __data.json
│ └── index.html
└── ja
├── __data.json
└── index.html
-
ssr = false
,trailingSlash = "never"
dir/file tree
$ tree ./build
./build
├── _app
│ ├── ...
│ ...
├── __data.json
├── index.html
├── en.html
├── ja.html
├── hello.html
├── hello
│ ├── __data.json
│ ├── world
│ │ └── __data.json
│ └── world.html
├── en
│ └── __data.json
└── ja
└── __data.json
html
タグlang
属性の設定
SEOにはあまり関係ないようだが綺麗になるようにしておく。
./src
$ tree ./src
./src
├── app.d.ts
├── app.html
├── hooks.server.ts
...
./src/app.html
<!doctype html>
<html lang="%lang%">
...
./src/hooks.server.ts
import type { Handle } from "@sveltejs/kit";
export const handle: Handle = async ({ event, resolve }) => {
const lang = event.url.pathname.startsWith("/ja") ? "ja" : "en";
const response = await resolve(event, {
transformPageChunk: ({ html }) => html.replace("%lang%", lang)
});
return response;
};
404等エラーページの設定
Cloudflare Pagesを使用しているため、その仕様に準拠したファイル名になっている。参考文献ではうまくいかないような記載があったが、現在の私の環境では+error.svelte
の内容で表示されるため、現在は特に問題ない挙動になっているように思われる。
svelte.config.js
import adapter from "@sveltejs/adapter-static";
...
const config = {
...
kit: {
adapter: adapter({
...
fallback: "/404.html",
...
}),
...
}
};
export default config;
./src/routes/+error.svelte
// you can design freely
<script lang="ts">
import { page } from "$app/state";
</script>
<h1>{page.status} : {page.error?.message}</h1>
+page
内でのlang
判別
各page.params
の中に格納されるため、そこを参照することで判別可能。指定がない場合はundefined
になる模様。
+page.svelte
<script lang="ts">
import { page } from "$app/state";
let langValue = $derived(page.params.lang ?? "undefined");
</script>
<!-- "undefined" or "en" or "ja" -->
<p>{langValue}</p>
雑記
SSGと言ってもSPAファイルを複製しているだけだと思うので、もうちょっと簡単にできてほしかったです。ssr = true
でないとcrawlが有効にならないことは公式に記載はありますがTroubleshooting項目内だけだと思うので機能説明時にアピールしてほしかったのもあります。trailingSlash = "never"
でうまくいかないのも謎です。
Discussion