今回は、Next.jsのISRをVercelを使わずに実装する
ということで
Vercel を使えば一発ですが Vercel を使わないで実装はできるのかやっていきます。
ざっと調べた感じ、キーワードとしては
- next.js
- serverless-nextjs
- AWS CLI
でした。
AWS はちょっと触ったことがある初心者です。
結構難易度高そうだなと思いつつやっていきます。
導入していく
next.js を init
$ npx create-next-app --example with-typescript-eslint-jest with-typescript-eslint-jest-app --use-npm
Serverless Framwork を導入
まずは、Serverless Framework
を install
global
に入れるように書いてあるけど、プロジェクトに含めてみた。
$ npm install -D serverless
確認
$ npx serverless --version
Framework Core: 2.44.0 (local)
Plugin: 5.2.0
SDK: 4.2.3
Components: 3.11.0
AWS CLI の設定する
次の工程調べてたら、AWS CLI の設定いろいろしなきゃみたいだった。
とりあえず、AWS アカウントを作成して設定してみる(まじで初心者)
aws configure
を実行
$ aws configure
AWS Access Key ID [None]:
Access Key を聞かれたので、AWS の cli について調べる…
アクセスキーはアクセスキー ID と秘密アクセスキーからなり、AWS に対するプログラムによるリクエストに署名するときに使用されます。アクセスキーがない場合は、AWS マネジメントコンソールから作成できます。ベストプラクティスとして、不必要なタスクでは、AWS アカウントのルートユーザーアクセスキーを使用しないでください。代わりに、ご自身のアクセスキーを持つ新しい管理者 IAM ユーザーを作成します 。
アクセスキー ID とシークレットアクセスキー
これですね。
IAM
でユーザー作ろうねってことみたいです。
ポリシーの作成
ポリシーの作成で必要な権限を設定…
必要な権限については AWS Permissions for deployment を参考にしていきます
sqs
の権限は ISR を使うときは必須なので、今回は追加しています。
ユーザーグループの作成
適当に名前をつけて、先ほど作成したポリシーを選択し保存
基本的にカスタムドメインで必要なもの以外選択しました
ユーザーの作成
- ユーザー名を適当に決める
- アクセスの種類は
プログラムによるアクセス
-
次のステップへ
- 先ほど作ったユーザーグループを指定
-
次のステップへ
- タグはつけず
次のステップへ
- 問題なければ
ユーザーの作成
を押して完了です
生成されたAccess Key
とSecret Access Key
をコピーしておいて…
Access Key
とSecret Access Key
を貼り付けて、あとはデフォのまま enter
多分、よくわからなかったら調べなさいと言われるだろうけど、とりあえず動かしたいってモチベなので…
$ export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
$ export AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
そして、export も設定してここは完了です!
Serverless Next.js Component を導入
serverless.yaml
を作成し…
serverless.yaml
myApp:
component: '@sls-next/serverless-component@1.20.0-alpha.4'
component を指定する
今回は ISR 対応したいので@sls-next/serverless-component@1.20.0-alpha.4
を指定
いったんデプロイしてみる
なんか、serverless.yaml
にinputs
を指定しているところが多いのですが、Zero-config
を掲げてたと思うのでいけるんじゃない?という気持ちでやっちゃいます。
好奇心だいじ
色々デプロイされ…
myApp:
appUrl: https://********.cloudfront.net
bucketName: ********
distributionId: ********
******************************************************************************************************************************************************************
Warning: You are using the beta version of Serverless Components. Please migrate to the GA version for enhanced features: https://github.com/serverless/components
******************************************************************************************************************************************************************
37s › myApp › done
Warning でてますが、ISR 対応がまだベータ版なのでしかたなしですね。
ISR 対応して確認していく
まずは、test/isr.tsx
を作成
test/isr.tsx
import React from 'react'
import { GetStaticProps } from 'next'
interface Props {
date: Date
}
export const getStaticProps: GetStaticProps = async () => {
return {
props: {
date: new Date().toLocaleString(),
},
revalipubDate: 5,
}
}
const Isr: React.VFC<Props> = ({ date }) => <p>{date}</p>
export default Isr
デプロイ
503 ERROR
The request could not be satisfied.
503 エラーでました。
ふて寝しそうです。
オーマイオーマイ…
見落としてました権限も修正しないとですね
sqs のところが*
になってますね
↓ 参考
Incremental static regeneration by kirkness · Pull Request #1028 · serverless-nextjs/serverless-next.js · GitHub
Adds Incremental Static Regeneration!
Related issues #804 #995
Add unit tests for regeneration logic
Test assumptions around how rollup.js bundles dynamic imports as per @dphang's comment
Add e2e tests for ISR fallbacks
Setup deployment of infra via serverless
Add e2e tests
Make code for ISR possible to be agnostic to the provider as per @dphang's comments
Only pass what's needed to the queue as per comment from @jvarho
Find a workaround for max 300 SQS calls per second (using lambda async invoke)
Setup deployment of infra via CDK
Create a demo site for reviewers - https://d3kjdl6gtxbc5l.cloudfront.net/about
Update cache logic to handle changes to revalidate property, as per @dvarho's comment
Load test the SQS queue to more than 300 requests in one seconds to test this assumption from @evangow - assumption was correct, it's max 300 API calls per second, will find a workaround
Tidy-up code by moving duplicate code (for example the page generation code) into functions/seperate files
The implementation looks like:
At "origin response" we check whether the response page is an ISR page (has an Expires header, or the original build page had a revalidate property)
We check whether the Expires header is in the future and if so apply a cache-control header such that the object is stored in the edge until its ready for revalidation.
If the Expires header is in the past then we trigger the page to be regenerated, whilst still returning the existing page with no-cache
To trigger a page rebuild we send a message to a FIFO SQS Queue, deduplicated using the identifier of the S3 object we are replacing so that we at most trigger one rebuild per page. FIFO SQS queues do come with a 300/second rate limit on all API Requests, which might be hit on very active sites, in which case we cache the page for an extra second to back-off requests.
You can check out a demo of this feature here, check the headers and the response times to try to get your head around its behaviour. The code for this page looks like:
export async function getStaticProps() {
return {
revalidate: 60,
props: {
date: new Date().toLocaleString()
}
};
}
const AboutPage = (props: {date: string}) => (
// ...
<p>This is the about page, and the time is about {props.date}</p>
// ...
)
export default AboutPage
👍
オーマイオーマイ…
もうこれでしたねしっかりと問題として上がってますね
Sure, let me take a look at the logs and let you know what happened
EDIT: looks like the Lambda itself doesn’t have permissions to access SQS endpoint:
{
"errorType": "AccessDenied",
"errorMessage": "Access to the resource https://sqs.us-east-1.amazonaws.com/ is denied.",
....
I’ll fix but we should also make sure to mention in Readme to give the Lambda itself permissions to access SQS (if it > > wasn’t obvious or already done)
↓ 参考
Incremental static regeneration by kirkness · Pull Request #1028 · serverless-nextjs/serverless-next.js · GitHub
Adds Incremental Static Regeneration!
Related issues #804 #995
Add unit tests for regeneration logic
Test assumptions around how rollup.js bundles dynamic imports as per @dphang's comment
Add e2e tests for ISR fallbacks
Setup deployment of infra via serverless
Add e2e tests
Make code for ISR possible to be agnostic to the provider as per @dphang's comments
Only pass what's needed to the queue as per comment from @jvarho
Find a workaround for max 300 SQS calls per second (using lambda async invoke)
Setup deployment of infra via CDK
Create a demo site for reviewers - https://d3kjdl6gtxbc5l.cloudfront.net/about
Update cache logic to handle changes to revalidate property, as per @dvarho's comment
Load test the SQS queue to more than 300 requests in one seconds to test this assumption from @evangow - assumption was correct, it's max 300 API calls per second, will find a workaround
Tidy-up code by moving duplicate code (for example the page generation code) into functions/seperate files
The implementation looks like:
At "origin response" we check whether the response page is an ISR page (has an Expires header, or the original build page had a revalidate property)
We check whether the Expires header is in the future and if so apply a cache-control header such that the object is stored in the edge until its ready for revalidation.
If the Expires header is in the past then we trigger the page to be regenerated, whilst still returning the existing page with no-cache
To trigger a page rebuild we send a message to a FIFO SQS Queue, deduplicated using the identifier of the S3 object we are replacing so that we at most trigger one rebuild per page. FIFO SQS queues do come with a 300/second rate limit on all API Requests, which might be hit on very active sites, in which case we cache the page for an extra second to back-off requests.
You can check out a demo of this feature here, check the headers and the response times to try to get your head around its behaviour. The code for this page looks like:
export async function getStaticProps() {
return {
revalidate: 60,
props: {
date: new Date().toLocaleString()
}
};
}
const AboutPage = (props: {date: string}) => (
// ...
<p>This is the about page, and the time is about {props.date}</p>
// ...
)
export default AboutPage
👍
最終的に、CloudFront / SQS / Lambda などなど削除し、プロジェクト内の.serverless
を削除してから
再度デプロイしたら正常に権限が適用され、問題なく動作することを確認しました!
参考
めっちゃ参考にさせてもらいました!
ありがとうございます…!