SapperとContentfulでSSGを行う
この記事はSvelte Advent Calendar 2020 1日目の記事です。
早速ですが本ブログはSapperとContentfulによるSSG(Static Site Generator)にて配信されています。
今回はこのブログの作り方を簡単に解説していきたいと思います。
近いうちにソースコードもGitHubのリポジトリをpublicにして公開しようかなと思います。
Sapperとは
Svelte Advent Calender1日目にいきなりSvelteではなくSapperで、はて?と思われる方もいるかも知れません。SapperはSvelteでSSR、SPA、SSGを行うためにパッケージされたフレームワーク・ツールです。ReactやVue.jsに馴染みがある方にはNext.js,Nuxt.jsと説明するとわかりやすいかと思います。
Svelteがベースになっているのでコンポーネントの書き方はSvelteそのものとなっています。
SapperのインストールとTypeScript化
このブログではTypeScriptで記述しています。SapperのデフォルトではJavaScriptになっているのでTypeScript化する必要がありますが、SapperTemplateにTypeScript化を行うスクリプトが同梱されているので簡単に導入することができます。
npx degit "sveltejs/sapper-template#rollup" sapper-blog
cd sapper-blog
node scripts/setupTypeScript.js
Static Site Generator
Sapperではnpm run export
を行うことでSSGが可能です。
しかし、そのままではSSGではなくSPAとして機能することに注意が必要です。
Sapperでデータを取得する
Sapperにはfetch
関数が用意されていて以下のようにコンポーネントでデータを取得できます。
Next.jsのgetInitialProps
やNuxt.jsのasyncData
に相当する機能です。
<script context="module">
export async function preload() {
const res = await this.fetch(`server-route.json`);
// ...
}
</script>
context="module"
はコンポーネントが評価される際に実行されるわけではなく、モジュールが評価される際に一回だけ実行されます。export
の場合はこのモジュールはコンパイルされた結果を保持します。
しかしこのままでは外部のAPIを取得する際には問題があります。
こちらのQiitaの記事に紹介されていました。
[Sapper] export で 完全静的サイトを作る [Svelte]
Contentfulのデータを取得する
Contentfulのデータは公式のライブラリを使います。
例えば個別記事の取得であれば[slug].svelte
と対になるようにデータ取得用のローカルエンドポイントとして[slug].json.ts
を作ります。
import { client } from '~/modules/contentful'
export async function get(req: any, res: any, _: any) {
const { slug } = req.params
const json = await client.getEntries({
content_type: 'article',
'sys.id': slug
})
if (json.items.length === 0) {
res.writeHead(404, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({
message: 'not found article'
}))
return
}
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify(json.items[0]))
}
※SapperのRequest,Responseの型定義がnpmで配信されてないので一旦anyにしている
import { createClient } from 'contentful'
export const client = createClient({
space: process.env.SAPPER_APP_CONTENTFUL_SPACE,
accessToken: process.env.SAPPER_APP_CONTENTFUL_ACCESS_TOKEN,
})
[slug].svelte
側ではcontent="module"
で以下のように取得できます。
<script context="module" lang="ts">
export async function preload({ params }): Promise<any> {
const res = await this.fetch(`${params.slug}.json`)
const post = await res.json()
if (res.status === 200) {
return { post }
}
this.error(res.status, post.message)
}
</script>
.envの読み込み
SapperにはどうやらNuxt.jsで言うNUXT_ENV_
でenvを自動で読み込む機能が無いようです。
require('dotenv').config()
client: {
input: config.client.input(),
output: config.client.output(),
plugins: [
replace({
'process.browser': true,
'process.env.NODE_ENV': JSON.stringify(mode),
'process.env.HOGE': process.env.HOGE
}),
このように一つ一つrollup.config.jsで定義することで読み込むことは出来ますが、毎回追加するのも手間なのでsapper-environmentを使いました。
これによってNUXT_ENV_
の用にSAPPAER_APP_
というプレフィックスの環境変数が自動で読み込まれます。
さいごに
要点だけかいつまんで説明しましたが同じ構成を作ろうとしてる方の参考になればと思います。
Sapper.jsというよりSvelte.jsはとても良く作られていて、コンポーネントを書くのが気持ちが良いフレームワークです(語彙が貧弱)
すでにVue.jsやReactに満足している方にも一度触ってもらいたいフレームワークです。