Code Lab 🤔

🤔

Next.jsのISRをVercelを使わずに実装する

#nextjs#npm#aws

公開:

今回は、Next.jsのISRをVercelを使わずに実装するということで
Vercel を使えば一発ですが Vercel を使わないで実装はできるのかやっていきます。

ざっと調べた感じ、キーワードとしては

  1. next.js
  2. serverless-nextjs
  3. 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 configure
AWS Access Key ID [None]:

Access Key を聞かれたので、AWS の cli について調べる…

アクセスキーはアクセスキー ID と秘密アクセスキーからなり、AWS に対するプログラムによるリクエストに署名するときに使用されます。アクセスキーがない場合は、AWS マネジメントコンソールから作成できます。ベストプラクティスとして、不必要なタスクでは、AWS アカウントのルートユーザーアクセスキーを使用しないでください。代わりに、ご自身のアクセスキーを持つ新しい管理者 IAM ユーザーを作成します 。

アクセスキー ID とシークレットアクセスキー

これですね。
IAMでユーザー作ろうねってことみたいです。

ポリシーの作成

ポリシーの作成で必要な権限を設定…

必要な権限については AWS Permissions for deployment を参考にしていきます

sqsの権限は ISR を使うときは必須なので、今回は追加しています。

ユーザーグループの作成

適当に名前をつけて、先ほど作成したポリシーを選択し保存
基本的にカスタムドメインで必要なもの以外選択しました

ユーザーの作成
  1. ユーザー名を適当に決める
  2. アクセスの種類はプログラムによるアクセス
  3. 次のステップへ
  4. 先ほど作ったユーザーグループを指定
  5. 次のステップへ
  6. タグはつけず次のステップへ
  7. 問題なければユーザーの作成を押して完了です

生成されたAccess KeySecret Access Keyをコピーしておいて…

再度aws configureする
  $ aws configure

Access KeySecret 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.yamlinputsを指定しているところが多いのですが、Zero-configを掲げてたと思うのでいけるんじゃない?という気持ちでやっちゃいます。

好奇心だいじ

  $ npx serverless

色々デプロイされ…

  
  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 👍

github.comのファビコン

github.com

Incremental static regeneration by kirkness · Pull Request #1028 · serverless-nextjs/serverless-next.js · GitHub

オーマイオーマイ…
もうこれでしたねしっかりと問題として上がってますね

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 👍

github.comのファビコン

github.com

Incremental static regeneration by kirkness · Pull Request #1028 · serverless-nextjs/serverless-next.js · GitHub

最終的に、CloudFront / SQS / Lambda などなど削除し、プロジェクト内の.serverlessを削除してから
再度デプロイしたら正常に権限が適用され、問題なく動作することを確認しました!

参考

めっちゃ参考にさせてもらいました!
ありがとうございます…!