この記事は何?
Amplify + Next.jsのプロジェクトで、「S3にフロントからアップロードした画像でページのOGPを設定したい」という要件があり、試行錯誤の末成功したのでそのまとめです。
ざっくりアウトライン
この記事でやることの実現方法のざっくりアウトラインをまずは示します
- Amplify + Next.jsからS3へのアップロードをする
- アップロード時にACLを
public-read
に設定する - SSRでS3の該当画像へのリンクを作成する
- OGPのメタタグを設定する
この中に普通にやると壁がいくつかあります。それをひとつずつどう解決したかを紹介していきます。
壁① S3へのACL設定アップロード
Amplify + Next.jsでのS3への画像アップロードはすでにさまざまな記事が出ています。実際、専用のコンポーネントライブラリなども公開されていますが、今回は独自のUIを実装するため、自分で公式フレームワークの関数を呼び出してアップロードする方法を考えます。
通常のアップロードは以下のコードで済みます。
const result = await Storage.put(fileName, data, {
level: 'public',
contentType: 'image/png'
})
この段階でも公開レベルを設定していますが、これはあくまでS3にアクセスするための認証情報に、誰の情報を利用できるかといったことを設定することしかできません。そのため、この状態で画像を取得する場合、それは有効期限付き、認証情報をパラメータに持ったURLを取得してそれで表示をすることになります。
この仕組みは通常のサービス内で画像を表示するという用途に関しては問題ありませんが、OGPの場合URLが有効期限付きというのはいただけません。
そこでS3にはACL設定というものが存在し、これを設定することで永続的な公開URLを作成できます。
この設定自体は公式のマニュアルにも存在が明記されており、上のコードを以下のように書き換えることで対応ができます。
const result = await Storage.put(fileName, data, {
level: 'public',
contentType: 'image/png',
acl: 'public-read'
})
ですが、これには一つ落とし穴があります。実はAmplifyのデフォルトの設定だと、S3のACL設定の書き換えが許されておらず、これをそのまま書くと403エラーが返ってきてしまいます。
そこでAmplifyのS3設定を手でいじる必要があります。変更する必要があるのは二箇所、s3-cloudformation-template.json
とsotrage-params.json
です。
まずCloudFormationのテンプレートの方ではs3:GetObject
などを探してみてください。これは自動生成の場合、おそらくAmplifyのAuthに設定したユーザーグループごとに設定テンプレートが生成されていると思います。
ここにs3:GetBucketAcl
, s3:PutBucketAcl
, s3:GetObjectAcl
, s3:PutObjectAcl
から必要なものを追加してあげましょう。Objectとつく方がないと自分の場合写真ごとの設定はうまくいきませんでした。最終的にはこのような形になると思います。(調べるといくつかこの解決法が出てきますが、Authの設定やバージョンによって若干jsonの見た目が異なると思います。ですが基本構造は同じなので、しっかりとどこを編集すればいいのか読み解いてあげましょう)
...
"hogeGroupPolicy": {
...
"Properties": {
...
"PolicyDocument": {
...
"Statement": [
{
"Effect": "Allow",
"Action": [
...
"s3:GetBucketAcl",
"s3:PutBucketAcl",
"s3:GetObjectAcl",
"s3:PutObjectAcl"
],
...
}
]
}
}
...
これに加えてstorage-params.json
の方にもAuthのグループごとに許可を与えてあげます。
例えばこのような形です。
...
{
"admin": [
"create/update",
"read",
"delete",
"updateacl"
]
}
...
この二箇所を編集してあげることで、ACLを設定して画像をアップロードすることができます。
壁② Amplify + Next.jsでのSSR
こちらは先日公式に対応してくれたのでそちらを用います。細かいところはさまざまな記事で既に述べられているのであまり詳細まで述べませんが、複数環境を運用している場合はamplify.yml
をプロジェクトフォルダ内で作ることで環境ごとに設定できます。
version: 1
backend:
phases:
build:
commands:
- amplifyPush --simple
frontend:
phases:
preBuild:
commands:
- nvm install 14.16.0
- nvm use
- yarn install
build:
commands:
- yarn run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
※以下に書いた問題は、こちらの問題が原因だったことが判明したので記述を削除させていただきました。
2021/8/2削除
~~なお「複数環境の場合」と言いましたが、この点に関しても注意点があります。 まず公式に言及されていますが、[Amplifyの公式](https://docs.aws.amazon.com/ja_jp/amplify/latest/userguide/server-side-rendering-amplify.html#ssr-and-ssg-branches)ではSSRとSSGのブランチが共存ができないことになっています。 これはおそらく自分の関わっているプロジェクトから問い合わせのエスカレーションで追加記載されたものだと思うのですが、実はこれは単にSSRとSSGの共存だけではなく、AmplifyのSSR時のURL設定の手法の方に本当の原因があります。~~ ~~というのも、この時問題になるのがAmplify側の生成するホスティングが効かなくなるということなのですが、これは何も異なるビルド方法の共存が原因ではありませんでした。AmplifyのSSRは裏側で専用のLambda@Edgeを立ち上げて、そのCloudFrontのURLにリダイレクトする形でAmplify Hostingを実現します。この時、リダイレクトに使うのがAmplifyにもともと備わっているリダイレクト設定機能なのですが、これが全環境共通の設定になってしまうため、全ての環境のリダイレクト先が一つの環境に向いてしまうのです。~~ ~~そのため、自分たちはAmplify Hostingをやめ、自分たちでルーティングを直接CloudFrontに向けることで解決しました。~~話は戻りますが、AmplifyでSSRをするポイントはAmplify.configure
を実行する場所です。こちらに注意書きがある通り、このissueが解決するまではgetServerSideProps
やgetStaticPaths
などを実行するページ全てに記載する必要があります。
つまりファイルとしてはこのような形になると思います。
// import文
Amplify.configure({...awsExports, ssr: true })
export const getServerSideProps: GetServerSideProps = async (context) => {
const SSR = withSSRContext()
const { params } = context
// 省略
return {
props: {
ogp_path: hogeImageUrl
}
}
}
// コンポーネントのコード
こうすることで_app.tsx
でpageProps
として値を取り出してあげることができるので、metaタグに設定すれば完了です。
ちなみに今回はSSRで行いましたが、2021年7月28日にひっそりとAmplifyのNext.js11対応とISR対応がリリースされたため、ISRを利用するというのも一つの手だと思います(検証はしていないため、動作は保証しません。)
(2021/08/02更新:ISRの動作検証の結果、既存のプロジェクトに導入するのはまだできない様子でした。この記事にあるように新規プロジェクトで行うとうまくいくようです。)
壁③ 公開URLの取得方法
最後の壁は実際にOGPのための画像のURLを取得する方法です。
AmplifyのStorageライブラリを使ってしまうと、有効期限つきのものしか取れないため、別の方法でURLをとる必要があります。
ここまでさりげなく端折ってきましたが、実際にAmplify + Next.jsからS3に画像をアップロードするときは
アップロードし→ファイルのkeyを取得し→それをデータベースに保存しておく
という流れになると思います。このkeyをStorage.get
に渡すことで本来はURLを取得するというわけです。
実はこのkeyはS3内でのフォルダのパスを表しています。例えばogpフォルダにアップロードした場合は、ogp/12345.png
といった具合に。
S3でのフォルダの一番トップレベルはアクセスレベルになっているので、public
に指定してあげた場合は/public/ogp/12345.png
というのが画像のS3内パスです。
このパスまでわかると、S3の公開アクセスURLは自分で作ることができます。具体的には以下のような形になります。
`https://${amplifyConfig.aws_user_files_s3_bucket}.s3.${amplifyConfig.aws_user_files_s3_bucket_region}.amazonaws.com/public/${key}`
ホスト名はS3のバケット名とバケットのあるリージョンで決定されます。そのためこれはaws-exports.jsから情報をとってくることができます。
またその下はパスを指定してあげればアクセスできます。そのためpublic
であげるということさえ決めておけば、このkeyの部分に先程のファイルのkeyが入ることで正しいURLになるのです。
まとめ
以上、Amplify + Next.jsで動的にOGPを設定する方法を紹介しました。
かなり裏技を駆使した感じになりましたが、昨年のリリースからなんだかんだSSR対応→ISR対応とかなりのスピード感で、進化してきているのもAmplifyの一面だと思うので、今後もっと簡単にできるようになるかもしれません。
そうだとしても、それまで同じことに悩んでいる人の一助になれば幸いです。