最新のNext.js機能App Routerに対応したGCSからのデータ取得の記事が見つからなくて困ったので書いてみました!
すでにGCS上にデータのアップロードが完了しているところからの説明になります。参考になれば嬉しいです!
この記事でわかること
最新のNext.js機能App Routerに対応した
- APIルートの作り方
- リクエストBodyの書き方
- 非公開GCS(Google Cloud Storage)から認証してもらう方法
1.APIルートを作る
1-1.APIルートを作るためのフォルダを用意する
AppRputerではこれまでと違い、下記のようなフォルダ構成にするとAPIルートになります
app/api
フォルダ内に任意のフォルダを作り、そのフォルダの名がAPIからデータを取るときのURL名になります(今回はgetProfImage
)
フォルダに保管するデータは共通でroute.js
にするところもこれまでと違いますね
my-app //名前は自由につけていいです//
├─ src
│ ├ app
│ ├ api
│ └ getProfImage //名前は自由につけていいです//
│ └ route.js
│ ├ page.js
│ └ favicon.ico
│
├─ .gitignore
├─ package.json
├─ package-lock.json
└─ README.md
1-2.route.jsを書く
ここはサーバーサイドでGCRにアクセスするための有効期間を指定してSigned URLを生成する機能を持ちます
サーバーサイドなのでより安全にURLを管理できるってことですね
環境変数は後で設定します
import { NextResponse } from "next/server";
import { Storage } from '@google-cloud/storage';
export async function GET(request) {
const urlParams = new URL(request.url).searchParams;
const fileName = `items/${urlParams.get('file')}`;
if (!fileName) {
return new NextResponse(JSON.stringify({ error: 'File name is missing' }), { status: 400 });
}
const storage = new Storage({
projectId: process.env.GCS_PROJECT_ID,
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS_JSON,
});
const bucketName = process.env.BUCKET_NAME ?? '';
const bucket = storage.bucket(bucketName);
const file = bucket.file(fileName);
try {
const options = {
version: 'v4',
action: 'read',
expires: Date.now() + 5 * 60 * 1000, // 5 minutes from now
};
const [url] = await file.getSignedUrl(options);
return new NextResponse(JSON.stringify({ url }), { status: 200 });
} catch (error) {
console.error('Failed to create signed URL:', error);
return new NextResponse(JSON.stringify({ error: `Failed to create signed URL: ${error.message}` }), { status: 500 });
}
}
NextResponse
を使う、GET
メソッドを明示する点がこれまでと違います
1-2-2.従来からの変更点解説(コピペだけしたい人はスルーして大丈夫です!)
- 従来
export default function handler(req, res){
...
return res.status(200).json({url})
}
- Approuter対応
GET
メソッドが明示され、res
がNextResponse
に置き換えられていますね
export async function GET(request) {
...
return new NextResponse(JSON.stringify({ url })
}
1-3.next.config.mjsに追記
GCSから画像を取ってくることを許可するために下記を追記します
images: {
domains: ['storage.googleapis.com'],
},
1-4.@google-cloud/storage モジュールがプロジェクトにインストール
下記コマンドをターミナルで叩き @google-cloud/storage パッケージをインストールします
npm install @google-cloud/storage
2.データを取ってくるサービスアカウントを準備する
GCPのページでデータを取得する権限のあるサービスアカウントをつくります
データを取ってきたいGCSのバケットのページ左上のハンバーガーアイコンをクリック
IAMと管理>サービスアカウント>をクリック
画面上部のサービスアカウントを作成をクリック
好きな名前とIDを入力
②このサービス アカウントにプロジェクトへのアクセスを許可する
でこのアカウントに付与したい権限のロールを選択します
PJの規模・運用方法に合わせて選択してください
その他はスルーして、認証キーをダウンロードします
作成したアカウントのキー
タブにある鍵を追加
をクリック
◉ JSON形式を選択して作成
をクリックしキーをダウンロードします
gcs_credential.json
と名前をつけて.env.local
と同じ階層に保管し
.gitignore
に追加します
# local env files
.env*.local
gcs_credential.json
キーは大切なデータなので絶対に.gitignoreに入れるのを忘れないでください
3..env.localに必要な情報を入れる
BUCKET_NAMEとGCS_PROJECT_ID
はPJによって違うので下記記載の通りに書いてみてください
BUCKET_NAME=バケットの名前を記載
GCS_PROJECT_ID= gcs_credential.json内にある"project_id"を記載
GOOGLE_APPLICATION_CREDENTIALS_JSON=./gcs_credential.json
.env.local内に記載する文字列は""
や’’
は不要で直接記載します
サーバーサイドで使用するキーなのでNEXT_PUBLIC_
は付けないでください
4.画像表示用のクライアントサイドのファイルを作る
ここまできたらあとは画像を表示するだけです!
先ほどサーバーサイドのroute.js
で作った時間制限のあるURLを使ってGCSにアクセスして画像を表示します
下記に全体のコードを示しますが、一例なのでPJに合わせて書き換えてみてください
import { useEffect, useState } from 'react';
import Image from 'next/image';
const fetchSignedUrlFromGCS = async (itemId, folder) => {
const fileName = `${folder}/${itemId}.png`;
const url = `/api/getProfImage?file=${encodeURIComponent(fileName)}`;
try {
const response = await fetch(url, {
headers: {
'Cache-Control': 'max-age=3600',
},
});
if (!response.ok) {
throw new Error(`Failed to load image with status: ${response.status}`);
}
const data = await response.json();
return data.url;
} catch (error) {
console.error('Error fetching signed URL:', error);
throw new Error(`Error fetching signed URL: ${error.message}`);
}
};
const GCSImageLoader = ({ imageUrl, error, width, height, objectFit = 'cover',quality , loadingStrategy}) => {
return (
<div style={{ position: 'relative', width: `${width}px`, height: `${height}px`, overflow: 'hidden' }}>
{error && <p>Error loading image: {error}</p>}
<Image
src={imageUrl}
alt={error ? "Failed to load image" : "Product Image"}
quality={quality}
placeholder="blur"
sizes="10vw"
fill
blurDataURL="/static/img/default-image.webp"
loading={loadingStrategy}
style={{
objectFit: objectFit,
objectPosition: 'center',
width: '100%',
height: '100%',
}}
/>
</div>
);
};
const ParallelImageLoader = ({ items, folder, width, height, quality ,index ,objectFit }) => {
const [images, setImages] = useState([]);
useEffect(() => {
const fetchImages = async () => {
const promises = items.map(async (item) => {
try {
const url = await fetchSignedUrlFromGCS(item.id, folder);
return { id: item.id, url, error: null };
} catch (error) {
return { id: item.id, url: '/static/img/default-image.png', error: error.message };
}
});
const results = await Promise.all(promises);
setImages(results);
};
fetchImages();
}, [items, folder]);
const loadingStrategy = index < 8 ? 'eager' : 'lazy';
return (
<>
{images.map((image) => (
<GCSImageLoader
key={image.id}
imageUrl={image.url}
error={image.error}
width={width}
height={height}
quality={quality}
loading={loadingStrategy}
objectFit= {objectFit}
/>
))}
</>
);
};
export { GCSImageLoader, ParallelImageLoader };
共通のポイント
URLに記載しているgetProfImage
はroute.js
を入れているフォルダ名にします
const url = `/api/getProfImage?file=${encodeURIComponent(fileName)}`;
PJごとに変える必要のある設定
- 今回は保管しているデータ形式が
.png
なので拡張子を指定しています。 - 別ルートでバックからもらっているitemIdに紐づけてデータを取ってこれるように、ファイル名はitemId.pngとして保管してもらっているので変数
${itemId}.png
として記載しています
const fileName = `${itemId}.png`;
- 画像を最適化できるNext.jsの機能を使用したいので
<Image>
タグを使用していますが、<image>
でも問題なく動きます
import Image from 'next/image';
・・・
<Image
src={imageUrl}
alt={error ? "Failed to load image" : "Product Image"}
quality={quality}
placeholder="blur"
sizes="10vw"
fill
blurDataURL="/static/img/default-image.webp"
loading={loadingStrategy}
style={{
objectFit: objectFit,
objectPosition: 'center',
width: '100%',
height: '100%',
}}
/>
参考にさせていただいたサイト
皆さんの情報を参考にさせていただきました。
本当にわかりやすい記事で感動しました、この場を借りてお礼させていただきたいです。ありがとうございます。
Next.js Appフォルダでの変更点を20個、時短で紹介
Next.jsからgcsに画像を登録したり呼び出したりしたい!!!!
最後に
最後まで読んでいただきありがとうございます!
AppRouter移行後の情報が少なく、悩んでしまったので記事にしてみました
少しでも参考になれば嬉しいです。