SvelteKitで作ったブログをCloudflare Pagesにデプロイしてみよう


## 概要

- CMSには**microCMS**を使用します
- ブログ記事を取得し、**SvelteKit**で**SG**して公開します
- デプロイ先には**Cloudflare Pages**を使用します

お目当ての記事ではなさそうだった場合、ねこだけでもみていってください。

### うちのねこ
[https://www.instagram.com/hokke_to_unagi/](https://www.instagram.com/hokke_to_unagi/)

## 自己紹介
Web制作会社で
- 社内向けのWebアプリ開発
- エンジニア部署周りのサポート
- サイト制作の手伝い
- マネジメント

などをしたりしています。

ここ1～2年のお気に入りはSvelteです。
よろしくお願いします。

## 前回書いた記事
1年ほど前にSvelteKitでブログを作る記事を書きました。

あれからしばらくしてルーティング周りが変わったりしています。
また、前回はデプロイまでやっていなかったので今回はそこもやってみようと思います。

::: note alert
SvelteKitはまだバージョン 1.0に到達していません。
本記事の情報は古くなっている可能性があるのでご注意ください。
:::

## microCMSで記事を作成する
[この辺](https://document.microcms.io/manual/signup)
から登録していってください。すでにある場合は不要です。

あとでサービスIDを環境変数の**VITE_SERVICE_DOMAIN**に登録するのでメモしておいて下さい。

:::note info
**忘れた人用**
https://hoge.microcms.io/
のhogeの部分です。
:::

リスト形式でAPIを作成し、フィールドは
- title（テキストフィールド）
- content（リッチエディタ）

で作成しておいてください。

APIが作れてフィールドも作成し終わったらテスト用に適当に記事をいくつか投稿しておいてください。

APIプレビューをクリックすると**X-MICROCMS-API-KEY**が画面に表示されているかと思うのでこちらも控えておいてください。
後ほど環境変数の**VITE_API_KEY**に登録します。

## 実装！
### package.json

``` package.json
{
	"name": "test-cloudflare-pages-sveltekit",
	"version": "0.0.1",
	"private": true,
	"scripts": {
		"dev": "vite dev",
		"build": "vite build",
		"preview": "vite preview",
		"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
		"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
		"lint": "prettier --check . && eslint .",
		"format": "prettier --write ."
	},
	"devDependencies": {
		"@sveltejs/adapter-cloudflare": "^1.0.0-next.31",
		"@sveltejs/kit": "next",
		"@typescript-eslint/eslint-plugin": "^5.27.0",
		"@typescript-eslint/parser": "^5.27.0",
		"eslint": "^8.16.0",
		"eslint-config-prettier": "^8.3.0",
		"eslint-plugin-svelte3": "^4.0.0",
		"prettier": "^2.6.2",
		"prettier-plugin-svelte": "^2.7.0",
		"svelte": "^3.44.0",
		"svelte-check": "^2.7.1",
		"svelte-preprocess": "^4.10.6",
		"tslib": "^2.3.1",
		"typescript": "^4.7.4",
		"vite": "^3.0.4"
	},
	"type": "module",
	"dependencies": {
		"microcms-js-sdk": "^2.2.1"
	}
}

```

- @sveltejs/adapter-cloudflare
- microcms-js-sdk

の2点を追加しています。

``` bash
npm install
npm run dev
```

### env
さきほど2点メモしてもらいました。

.envを作成し、それぞれ追記します。
``` .env
VITE_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
VITE_SERVICE_DOMAIN=hoge
```

### svelte.config.js
adapterを@sveltejs/adapter-cloudflareに入れ替えます。
SGしたい場合も@sveltejs/adapter-staticにする必要はなく、@sveltejs/adapter-cloudflareで可能です。

``` svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	preprocess: preprocess(),
	kit: {
		adapter: adapter(),
		prerender: {
			default: true
		},
		alias: {
			$assets: 'src/assets',
			$components: 'src/components'
		}
	}
};

export default config;

```

#### prerender
SGするための設定項目です。
各ページで`export const prerender = true;`を設定してもOKですが、ブログなら前ページSGするかと思うのでここで一括指定します。

### vite.config.js
``` vite.config.js
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig(() => {
	return {
		server: {
			host: true,
			port: 3000
		},
		plugins: [sveltekit()]
	};
});
```

#### server
こだわりがあるわけではないですが、localhost:3000でみたいので設定します。

### routesディレクトリ
今まで`index.svelte`などのファイル名でしたが、`+page.svelte`などに変わっています。

TOPページは今回どうでもいいのでとりあえず以下で作ります。

``` src/routes/+page.svelte
<svelte:head>
	<title>Top Page</title>
</svelte:head>
<h1>Top Page</h1>
<a href="/posts" sveltekit:prefetch>Posts</a>
```

### microcms-js-sdk
そろそろ記事の取得周りを行いたいので、microcms-js-sdkの設定をやっていきます。

以下のファイルを作成します。

``` src/libs/client.ts
import { createClient } from 'microcms-js-sdk';
export const client = createClient({
	serviceDomain: import.meta.env.VITE_SERVICE_DOMAIN,
	apiKey: import.meta.env.VITE_API_KEY
});
```

先ほど`.env`に設定した環境変数をこちらで読み込みます。
Cloudflare Pagesにデプロイ時（ビルド時）は管理画面で設定する環境変数が読み込まれます。のちに設定します。

クライアントが作成できたので、いよいよ記事一覧と詳細を作っていきます。

### 記事一覧
routesディレクトリに以下のファイルを作成します。

``` routes/posts/+page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<svelte:head>
	<title>Posts</title>
</svelte:head>
<h1>Posts</h1>
{#if data.contents}
	<ul>
		{#each data.contents as content}
			<li>
				<a href="/posts/{content.id}/" sveltekit:prefetch>
					{content.title}
				</a>
			</li>
		{/each}
	</ul>
{/if}
```

::: note info
`export let data`
の部分ですが、固定のようです。
同階層のディレクトリの+page.server.tsからデータを受け取ります。
:::

``` routes/posts/+page.server.ts
import { error } from '@sveltejs/kit';
import { client } from '../../libs/client';
import type { MicroCMSListResponse } from 'microcms-js-sdk';
type Post = {
	id: string;
	createdAt: string;
	updatedAt: string;
	publishedAt: string;
	revisedAt: string;
	title: string;
	content: string;
};
/** @type {import('@sveltejs/kit').RequestHandler} */
export async function load() {
	const res = await client.get<MicroCMSListResponse<Post>>({
		endpoint: 'bar'
	});
	if (res) {
		return { ...res };
	}
	throw error(404, 'Not found');
}

```

::: note info
endpointのところは自分で作成したものに変更ください。
以下の**bar**の部分です。
https\://hoge.microcms.io/api/v1/**bar**
:::

記事一覧を取得した場合レスポンスは以下のようになっています。

``` json
{
    "contents": [
        {
            "id": "idididid",
            "createdAt": "2021-09-07T07:24:44.764Z",
            "updatedAt": "2022-01-10T09:28:24.615Z",
            "publishedAt": "2021-09-07T07:24:44.764Z",
            "revisedAt": "2022-01-10T09:28:24.615Z",
            "title": "仮記事",
            "content": "<p><strong>本文</strong></p>"
        }
    ],
    "totalCount": 1,
    "offset": 0,
    "limit": 10
}
```
resにこちらが入ってくるので、load関数の中で展開してreturnします。

::: note info
ここでreturnしたものが先ほどの`export let data`のdataに入ってきます。

なので`+page.svelte`のテンプレート部分で`{#each data.contents}`のようにして表示できていたんですね。
:::

microCMSの一覧取得時、他にもtotalCount、offset、limitが返ってくるのでそちらでページネーションの実装も可能です。

これで記事一覧がlocalhost:3000/postsで見れるようになっているはずです。

### 記事詳細
続いて記事詳細も作っていきましょう。
こちらに関してもやることは一緒です。

以下のファイルを作成します。

``` src/routes/posts/[slug]/page.svelte
<script>
	/** @type {import('./$types').PageData} */
	export let data;
</script>

<svelte:head>
	<title>{data.title}</title>
</svelte:head>
<article>
	<h1>{data.title}</h1>
	<time datetime={data.publishedAt}>{data.publishedAt}</time>
	{@html data.content}
	<p><a href="/posts" sveltekit:prefetch>Back To Posts</a></p>
</article>
```
記事一覧で`data.contents`に配列で入っていた内容をそのまま出しているだけですね。


``` src/routes/posts/[slug]/page.server.ts
import { error } from '@sveltejs/kit';
import { client } from '../../../libs/client';
import type { MicroCMSObjectContent } from 'microcms-js-sdk';
type Post = {
	id: string;
	title: string;
	content: string;
};
type Props = {
	params: {
		slug: string;
	};
};
/** @type {import('@sveltejs/kit').RequestHandler} */
export async function load({ params }: Props) {
	const res = await client.get<MicroCMSObjectContent & Post>({
		endpoint: 'bar',
		contentId: params.slug
	});
	if (res) {
		return { ...res };
	}
	throw error(404, 'Not found');
}

```
やっていることは同じですが、load関数の引数に`param`を渡しています。

今回ディレクトリ名に[slug]とつけたので、
localhost:3000/posts/helloとした場合helloがslugに入ってきます。

テンプレート側はこれでおしまいです。
~~個人的にはindex.json.jsなどを置いていた時のほうがわかりやすかった気がしますね。~~

### Cloudflare Pages
とりあえず登録してください。

登録したら左サイドバーにPagesというのがあるのでクリックして、プロジェクトを作成します。


![スクリーンショット 2022-08-23 183327.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1677134/b03ccdd6-6fd7-c532-d17e-4f37cc617128.png)

Gitに接続をクリックするとGitHubアカウントの選択と、リポジトリの選択項目が出てきます。

リポジトリが表示されていない場合は、
> GitHub のCloudflare Pagesのアプリに対するリポジトリ アクセスを構成します。

の部分にリンクがあるので、デプロイしたいリポジトリを選択します。

選択するとリダイレクトがかかって元の画面に戻ってくるので、選択してセットアップの開始をクリックします。

![スクリーンショット 2022-08-23 183919.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1677134/f6b259f8-68d3-5d5c-5970-f30d59616656.png)

遷移後のページでフレームワーク プリセットをSvelteKitに選択すると、勝手に項目を埋めてくれます。
楽ちんですね！

環境変数 (アドバンスド)という項目があるので、さっき`.env`に設定したものをこちらにも登録しましょう。

![スクリーンショット 2022-08-23 184152.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1677134/2248c627-6c11-4053-bc9c-58ec15d8fac5.png)

::: note warn
注意
ここで、NODE_VERSIONに17以上（16.17.0以上）を指定する必要があります。
バージョンをこれで指定します。
:::

「保存してデプロイする」をクリックするとデプロイされます。
お疲れさまでした。

## 躓いた点
- envの読込は VITE_付きでないとだめ
https://kit.svelte.dev/docs/modules#$env-dynamic-private
この辺を使うのかと思った。使ってみたがうまくいかなかった。
- env指定時、VITE_付きで指定するとビルド後のファイルに残ってしまうのでは？と思ったのだが、+page.server.tsで使用するとビルド時に取得後のデータをjsonで持つようになるので問題なかった。（デプロイされたサイトのソースを見てみてください）
- データ取得
+page.svelteでAPIをたたく関数を作って取得するとビルド後のファイルにenvで設定したキーがそのまま残ってしまう
- ルーティングが変わっているのに気づいていなかった
https://kit.svelte.dev/docs/routing

## 解決できていない問題
svelte.config.jsでconfig.kit.trailingSlashを'always'にすると/posts/ページのみリロードで404になる

ローカルでのビルドでもpostsディレクトリにindex.htmlが生成されているので404にはならないと思うのですが、なぜか404になります。

他にも設定項目があるので何か設定漏れがあるのかもしれませんが、本記事では解決できていないのでtrailingSlashを設定していません。

## 感想
個人ブログもCloudflare Pagesにデプロイしているのですが、アナリティクスも設定できたりR2というストレージに画像もアップできそうなのでいいなと思っています。

Cloudflare Workersを使えばBASIC認証もかけられるので便利です。
使ってみてはどうでしょうか？