SvelteKit で SSG するだけの男

# はじめに

## 本記事で書いていること

本記事は本当にただ SSG として動くいわゆるハローワールドやってみた（API からのフェッチはする）しか扱っていません。記載しているコードも公式ドキュメントをそのまま転用しているだけだったりしているところも多いです。

Jamstack については素晴らしい記事が本アドベントカレンダー後半で投稿されますのでそちらをご覧ください。

## ベンチ要員

SI アドベントカレンダー2022、素晴らしいことに定員が埋まっておりましたが、もしかしたら業務上の理由などで投稿が間に合わない方などいらっしゃる可能性があるので、とベンチを温めていました。

## なんで SvelteKit で SSG

[The State of JS 2021: フロントエンドフレームワーク](https://2021.stateofjs.com/ja-JP/libraries/front-end-frameworks)

によると最近熱いらしい Svelte。

名前をちらほら聞いておりまして、
そんなタイミングでたまたまイベント告知ページを作ってくれやと知人に頼まれ、
またたまたまプロジェクトのメンバーの一人が Jamstack が熱いと教えてくれたこともあり、
じゃあ SvelteKit 使ってみよ、ということになりました。

# 環境

``` shell-session
$ node -v
v18.12.1
$ npm -v
8.19.2
$
```

VSCode でやります。

[Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode)

# プロジェクト作成

`npm create svelte@latest {プロジェクト名}` を実行します、色々あれ入れるかこれ入れるか聞いてくれてあたたかい。

``` shell-session
$ npm create svelte@latest my-app

create-svelte version 2.0.0-next.199

Welcome to SvelteKit!

This is release candidate software; expect bugs and missing features.

Problems? Open an issue on https://github.com/sveltejs/kit/issues if none exists already.

✔ Which Svelte app template? › Skeleton project
✔ Add type checking with TypeScript? › Yes, using TypeScript syntax
✔ Add ESLint for code linting? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
✔ Add Playwright for browser testing? … No / Yes
✔ Add Vitest for unit testing? … No / Yes

Your project is ready!
✔ Typescript
  Inside Svelte components, use <script lang="ts">
✔ ESLint
  https://github.com/sveltejs/eslint-plugin-svelte3
✔ Prettier
  https://prettier.io/docs/en/options.html
  https://github.com/sveltejs/prettier-plugin-svelte#options
✔ Playwright
  https://playwright.dev
✔ Vitest
  https://vitest.dev

Install community-maintained integrations:
  https://github.com/svelte-add/svelte-adders

Next steps:
  1: cd my-app
  2: npm install (or pnpm install, etc)
  3: git init && git add -A && git commit -m "Initial commit" (optional)
  4: npm run dev -- --open

To close the dev server, hit Ctrl-C

Stuck? Visit us at https://svelte.dev/chat

$
```

`my-app` ディレクトリに移動して `npm install` したら `npm run dev` でローカルで動かせます。

# SSG にしていく

ググればいろんな人が教えてくれているんですが、ビルドする時に API とかからデータとってサーバで HTML （レンダリング済） 作ってサーバに置いておくってやつですな。ビルドしないと内容変わらないから、ブログとか Wiki には向いている。

## SSG 用パッケージのインストール

``` shell-session
$ npm install -D @sveltejs/adapter-static
...
$
```

## 設定 js 修正

``` JavaScript:svelte.config.js 
import adapter from '@sveltejs/adapter-static'; -- 使用する　adapter を上でインストールしたものに変える
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://github.com/sveltejs/svelte-preprocess
	// for more information about preprocessors
	preprocess: preprocess(),

	kit: {
		adapter: adapter()
	}
};

export default config;

```

## prerenderable

この段階で `npm run build` を実行すると、失敗します。

``` shell-session
$ npm run build
...
> Using @sveltejs/adapter-static
  @sveltejs/adapter-static: all routes must be fully prerenderable, but found the following routes that are dynamic:
    - src/routes/
  
  You have the following options:
    - set the `fallback` option — see https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode for more info.
    - add `export const prerender = true` to your root `+layout.js/.ts` or `+layout.server.js/.ts` file. This will try to prerender all pages.
    - add `export const prerender = true` to any `+server.js/ts` files that are not fetched by page `load` functions.
  
    - pass `strict: false` to `adapter-static` to ignore this error. Only do this if you are sure you don't need the routes in question in your final app, as they will be unavailable. See https://github.com/sveltejs/kit/tree/master/packages/adapter-static#strict for more info.
  
  If this doesn't help, you may need to use a different adapter. @sveltejs/adapter-static can only be used for sites that don't need a server for dynamic rendering, and can run on just a static file server.
  See https://kit.svelte.dev/docs/page-options#prerender for more details
error during build:
Error: Encountered dynamic routes
    at adapt (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/@sveltejs/adapter-static/index.js:53:12)
    at adapt (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/@sveltejs/kit/src/core/adapt/index.js:28:8)
    at Object.handler (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/@sveltejs/kit/src/exports/vite/index.js:543:12)
    at async PluginDriver.hookParallel (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/rollup/dist/es/shared/rollup.js:22670:17)
    at async Object.close (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/rollup/dist/es/shared/rollup.js:23750:13)
    at async Promise.all (index 0)
    at async build (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/vite/dist/node/chunks/dep-5605cfa4.js:45548:13)
    at async CAC.<anonymous> (file:///Users/k.sakaguchi/Documents/temp/my-app/node_modules/vite/dist/node/cli.js:756:9)
$
```

書かれているように、すべてのルートで prerender できるようにしないとダメだぞと怒られます。

SvelteKit では `src/routes/` 配下のディレクトリ構成がそのままルーティングになります。この際、このディレクトリ構成範囲外の URL にアクセスされた時どうするねんってのを指定しろということですな。

SPA であれば fallback オプションを使えーいとかありますが、今回は SSG ですので、fallback とかは指定しないです。二個目の `export const prerender = true` を含んだ `+layout.server.ts` を配置します。

SvelteKit では `*.svelte` ファイルがページを、 同名の `.js / .ts` で API などからのデータを取得する load 関数を実装するという仕組みです。

`+layout.svelte` は全ページで表示される内容を実装するのですが、今回は SSG するだけの男なので見た目とかありません、 `+layout.server.ts` だけを実装します。

``` TypeScript:+layout.server.ts
export const prerender = true;
```

これで `npm run build` が通るようになります。

# データを load する

ここで、ビルド時に API からデータを取得する部分の実装をします。デフォルトで配置されている `+page.svelte` が下記になります。

``` Svelte:+page.svelte
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>

```

このファイルの修正と、このページ用のデータを取得する `+page.server.ts` を追加します。

今回ヘッドレス CMS として microCMS を使用してます。

環境変数は Netlify で設定できますが、`.env` ファイルを作成してそこから取得することも可能です。

``` TypeScript:+page.server.ts
import { error } from '@sveltejs/kit';
import { API_KEY } from '$env/static/private'
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async () => {
    const headers: HeadersInit = new Headers();
    const apiKey: string = API_KEY ?? "";
    console.log(apiKey)
    headers.set("X-MICROCMS-API-KEY", apiKey)
    const res = await fetch('https://example.microcms.io/api/v1/hoge', {
        headers: headers
    });
    const resJson = await res.json()
    console.log(resJson)
    if (res.ok) {
        return await resJson;
    }

    throw error(404, 'Not found');
}
```

環境変数から API-KEY を取得し、リクエストを投げています。

`env` を設定で指定します、`.env` の配置している場所を指定するため、もう一度 `svelte.config.js` を修正します。

``` JavaScript:svelte.config.js 
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	// Consult https://github.com/sveltejs/svelte-preprocess
	// for more information about preprocessors
	preprocess: preprocess(),

	kit: {
		adapter: adapter()
	},

	env: {
		dir: process.cwd()
	}
};

export default config;

```

取得した値を `+page.svelte` で反映します。

``` Svelte:+page.svelte
<script lang="ts">
    import type { PageServerData } from './$types';
  
    export let data: PageServerData;
</script>

<h1>{data.contents[0].fuga}</h1>
<p>Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to read the documentation</p>
```

`npm run dev` で実行すると、 `console.log()` の内容がブラウザ側ではなく実行しているターミナルに表示され、サーバで動いてるんやでってのが分かりますね。

環境変数の取り方も 4 種類あります、

- `$env/static/private`
- `$env/dynamic/private`
- `$env/static/public`
- `$env/dynamic/public`

詳しい説明は [ドキュメント](https://kit.svelte.dev/docs/modules#$env-dynamic-private) に任せますが、SSG でビルドする時に必要なだけなので、今回は `$env/static/private` を使用してます。

# Jamstack

今回の記事の対象ではないので簡単に書きますが、SSG はビルドしたファイルをサーバに配置して配信するのですが、流石にブログでも更新するたびに自分でビルドしてデプロイしてというわけには行きません。はじめに書いたように、いい感じにやる Jamstack という構成を今回使用しました。

microCMS で API を作成し、ホスティングサービスとして Netlify を選択。

今回作成した SSG アプリとくみあわせると、

- microCMS で API のレスポンスの値を変更したタイミング
- main ブランチが更新されたタイミング

でビルドしてデプロイという CD をしてくれる仕組みをかなり簡単に作れます。いいね。

はじめにでも書きましたが、詳しくは SI アドベントカレンダー後半の記事を要チェックや。

以上、拙文失礼しました。

# 参考

[Introduction • Docs • SvelteKit](https://kit.svelte.dev/docs/introduction)
[icroCMS + Svelte + Sapper + TypeScriptでJamstackブログを作ってみよう](https://blog.microcms.io/microcms-svelte-sapper-jamstack-blog/)
[Welcome to Netlify | Netlify Docs](https://docs.netlify.com/)