🌀
Blitz.jsで実装したページをiframeに読み込むとqueryが無限ループする件を回避する
概要
- Blitz.jsでWebアプリケーションを作っている
- あるページを外部サービスでiframeで埋め込んで使いたい要件がある
- あるページはURLクエリに応じたデータを読み込み表示する機能
- STUDIOのiframeモジュールで読み込ませて検証した
現象
- STUDIOのデザインエディタ、プレビューともに該当のデータが表示されない
- Webインスペクタを見たら毎秒数回API(
useQuery()
のリクエスト)を叩きまくっている - ヤバい
原因
- Blitz.jsでは
react-query
を内部でラップして実装している。 - その
useQuery()
の内部でBlitz.jsの管理するセッションが新しくなったら自動でキャッシュを破棄してrefetchする処理があるようです。 - 通常のページへのアクセスと違い、iframeに読み込ませた場合(セッションを保持できず?)無限ループ的にrefetchし続けてしまう。
- 同じ現象についての公式リポジトリのissueがあった
解決方法
sessionMiddleware()
の設定を変える
- issueで提示されている
blitz.config.ts
のsessionMiddleware()
のオプションでsameSite: "none"
を設定する。 - ただしこれは
https://~
のみで動作するとのこと。(npm run dev
などlocalhostだと確認できない)し、secureCookies: true
を併用したい場合、Safariだとうまくいかないという話題もある(が英語が自信ないのと確認していないので要検証)
API Routes経由でデータを取得する
- 自分が採用したのはAPI Routesでデータを取得する方法。
- 今回の要件は完全にパブリックな埋め込み専用のページなので限定的なユースケースのAPIなので使い回しやビジネスロジックがほとんど不要だったので割り切れるため。
エントリーポイント
- Next.jsと違って
{src}/page/api/*
の代わりに{src}/api/*
に置く。(pagesのままでも動く)
/api/some-data/[id].ts
ハンドラの実装
import { BlitzApiHandler } from "blitz"
import db from "db"
// Promise<T> -> T にするUtilityType
import Unpromise from "app/utils/Unpromise"
// string | string[] | undefined -> string | undefined にする関数
import parseQueryValue from "app/helpers/parseQueryValue"
const fetchData = async (id: string) => {
return await db.someData.findFirst({
where: { id },
// このページで使いたいデータだけ取り出す。
select: {
id: true,
name: true,
description: true,
image: true,
},
})
}
type Response = Unpromise<ReturnType<typeof fetchData>>
const handler: BlitzApiHandler<Response> = async (req, res) => {
const {
query: { id },
} = req
const idParam = parseQueryValue(id)
if (!idParam) {
res.statusCode = 400
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ error: "idParam is required" }))
return
}
const data = await fetchData(idParam)
if (!data) {
res.statusCode = 404
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ error: `${idParam} is not found.` }))
return
}
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify(data))
}
export default handler
export type GetDataResponse = Response
利用するコード
import { GetDataResponse } from '/api/data/[id]'
const fetchData = (id : string) => {
const response = await fetch(`/api/some-data/${id}`)
const data = await response.json()
// 中略
return data as GetDataResponse // fetch()したレスポンスへのTypeアサーションはご自由に
}
注意点
-
<Suspense>
など向けの実装は自分でやる必要がある。
Discussion