RemixでLoaderFunctionからloaderHeadersにデータが渡らない
RemixでmicroCMSのプレビュー機能を利用するため、draftKeyが付与された時にheaderを動的に切り替えたい。loaderHeaders.get('Cache-Control')
には必ずnull
が入ってしまう。
// dataLoaderからheaderが渡された場合に切り替える。デフォルトはstale-while-revalidate
export const headers: HeadersFunction = ({ loaderHeaders }) => {
const cacheControl =
loaderHeaders.get('Cache-Control') ??
'max-age=0, s-maxage=60, stale-while-revalidate=60';
return {
'Cache-Control': cacheControl,
};
};
// microCMS APIから記事詳細を取得する
export const loader: LoaderFunction = async ({ params, request }) => {
// 下書きの場合
const url = new URL(request.url);
const draftKey = url.searchParams.get('draftKey');
// 記事を取得
// 下書きの場合キャッシュヘッダを変更
const headers = draftKey
? { 'Cache-Control': 'no-store, max-age=0' }
: undefined;
return json(content, { headers });
};
暫定の解決策
app/root.tsx
にLoaderFunction
を追加。
親となるrootのloaderに何かしらデータがあるもしくは明示的にnull
であれば処理がうまくいっている模様。
export const loader: LoaderFunction = async () => {
return null;
};
暫定の解決策に至るまでの経緯
探ってみたところ同じ現象はすでに下記issueで報告されていた。
データの流れは下記のような感じ
dataLoader -> middleware(json helper) -> headerLoader
指摘箇所はjson helper
だったが、helper function
を書き換えるのは他にも影響あるかと思いdataLoader
側から該当箇所を出力する部分を調整するPRを投げた。
開発者のryanflorence氏からフィードバックをもらうが、ryanflorence氏の環境では該当の問題は起きていないとissueをクローズしてしまった。
いやいや、ということで、症状を再現するテストコードとcodesandbox環境を用意した。
改めてryanflorence氏からフィードバック。
Ah we have regression here, at the moment, headers incorrectly requires that every parent route has a loader. We'll fix this soon, thanks for your work on this :)
どうやら、rootのloaderからもloaderが渡ってくる前提でheaderLoaderのロジックが組まれてしまっていることが原因らしい。
ということでissueは再オープン。
さらに調べた感じ下記getDocumentHeaders
で問題が発生する。
ここに渡す前に正しくデータを用意できればよいのかもしれない。
引続き調査。
function getDocumentHeaders(build, matches, routeLoaderResponses, actionResponse) {
// matches is [root, child], routeLoaderResponses is [child]
return matches.reduce((parentHeaders, match, index) => {
// index 0, routeModule is root
let routeModule = build.routes[match.route.id].module;
// index 0, response is from child (since I don't have a root loader)
let loaderHeaders = routeLoaderResponses[index] ? routeLoaderResponses[index].headers : new Headers();
let actionHeaders = actionResponse ? actionResponse.headers : new Headers();
// since root doesn't export headers, i end up with undefined and lose my headers :(
let headers = new Headers(routeModule.headers ? typeof routeModule.headers === "function" ? routeModule.headers({
loaderHeaders,
parentHeaders,
actionHeaders
}) : routeModule.headers : undefined);
// Automatically preserve Set-Cookie headers that were set either by the
// loader or by a parent route.
prependCookies(actionHeaders, headers);
prependCookies(loaderHeaders, headers);
prependCookies(parentHeaders, headers);
return headers;
}, new Headers());
}
修正してくれた🙏