オブジェクト(例えば記事)のプレビューを実装してみました。実装方法は「Advanced Features: Preview Mode | Next.js」で解説されていますが、当サイトの場合はpage/preview.js
の新規実装とpage/blog/[basename].jp
の編集、そしてAmplifyへの環境変数の設定を行いました。
PowerCMS Xのプレビューボタンを押すと記事のベースネームとシークレットトークンを付けてプレビューのリクエストを送信します。プレビューボタンは「管理画面のカスタマイズ | PowerCMS X」で紹介されているように代替テンプレートを用意したのですが、今後プラグイン化しようと考えています。(※2021年11月5日追記:プラグイン化が完了しています。)
シークレットトークンについてはPHPでecho bin2hex( random_bytes( 16 ) );
を使い生成してみました。下書きの記事をAPIで取得するには必要な権限を有するユーザーで認証を行う必要があります。結果、page/preview.js
は以下のようなコードになりました。
import { client } from '../../libs/client';
export default async function handler(req, res) {
if (req.query.endpreview) {
// /api/preview?endpreview=1でアクセスするとプレビューデータをクリアする
res.clearPreviewData();
res.json({ message: 'Successfully cleared the preview mode cookie.' })
res.end();
return;
}
if (req.query.secret !== process.env.CMS_PREVIEW_TOKEN || !req.query.basename) {
return res.status(401).json({ message: 'Invalid token' })
}
// ユーザー認証
const name = process.env.CMS_USERNAME;
const password = process.env.CMS_PASSWORD;
const authResponse = await client.authentication(name, password);
if (!authResponse.ok) {
return res.status(401).json({ message: 'Authentication faild.' });
}
// 未公開オブジェクトの取得
const authData = await authResponse.json();
const token = authData.access_token;
const options = {
basename: req.query.basename, // idを利用してもよいかもしれないと考えている
cols: 'basename',
};
const response = await client.getObject('entry', null, 1, options, token); // 本稿ではモデルが固定になっている
if (!response.ok) {
return res.status(404).end();
}
// 情報を設定してリダイレクト処理
const json = await response.json();
res.setPreviewData({
basename: json.basename,
token: token, // 認証で取得したトークンを渡す
});
// Redirect to the path from the getObject.
// Don't redirect to req.query.slug as that might lead to open redirect vulnerabilities.
res.redirect(`/blog/${json.basename}`);
};
page/blog/[basename].jp
はプレビュー情報を持つ場合と通常の場合で分岐を行い処理をします。大きな変更はなく、以下のようなコードになりました。
export const getStaticProps = async (context) => {
let token = null;
let options = {
cols: 'title,text,excerpt,published_on',
};
if (context.preview) {
// プレビューの場合
options['basename'] = context.previewData.basename;
token = context.previewData.token;
} else {
// 通常の場合
options['basename'] = context.params.basename;
}
const response = await client.getObject('entry', null, 1, options, token);
const entry = await response.json();
return {
props: {
entry,
isPreview: context.preview ? true : false,
}
}
};
コードをGitHubにPushしてAmplifyでビルドが完了したのですが、プレビューを実行するとCloudFrontが503エラーが発生しました。どうしたものかと考えたのですが、「WebサイトにCloudFront Functionを設定したら接続が503エラーとなったのでトラブルシュートしてみた | DevelopersIO」を参考にログを探して確認したところ、環境変数が上手く扱えていないようでした。「Amplify+Next.js 環境変数設定方法まとめ」を読んで知ったのですが、環境変数についてnext.config.js
に記述が必要でした。
プレビューが上手く行ったと思ったのですがそのままだと通常のページが正しく表示されなくなったので、プレビュー表示後すぐにブラウザ側で/api/preview?endpreview=1
をfetch
してプレビューデータを消すことにしました。
現在プレビュー対象モデルがentry
固定になっているので、他のモデルにも対応させる方法を今後考えてみます。(クエリストリングで受け取るのだろうなと考えています。)