2024/12/02 投稿
Next.jsのSSGで作成したこのブログにogpを追加しようとしてつまずいたので、その解決方法を紹介します。また、CloudFrontでのつまずきも合わせて紹介します。
「そんなのあたりまえだろ」と思うこともあるかもしれませんが、私のような初心者にとってはつまずきどころだったので、解決方法を共有します。
1年前にも同じようなことをやろうとして、結果的にうまくいきませんでした。以下の記事曰く、next.js側の問題だったそうなのであきらめていました。
一年たった今なら、解決していると信じて再挑戦しました。
⨯ Error: export const dynamic = "force-static"/export const revalidate not configured on route "/opengraph-image" with "output: export". See more info here: https://nextjs.org/docs/advanced-features/static-html-export
Error: Catch-all must be the last part of the URL.
app/ ├── opengraph-image.tsx ├── [...slug]
app/ ├── (shared)/ │ ├── opengraph-image.tsx ├── [...slug]
import { ImageResponse } from 'next/og';
import fs from 'fs';
import path from 'path';
import { getCategory, getPost } from "@/app/main";
import { category } from '@/app/type';
export const dynamic = "force-static";
export const size = {
width: 1200,
height: 630,
};
export const contentType = 'image/png';
export const generateStaticParams = async () => {
const categories: category[] = await getCategory();
return categories.flatMap((category) =>
category.posts.map((post) => ({
categoryId: post.slug[0],
postId: post.slug[1],
}))
);
};
export default async function ImageOG({ params }: { params: { categoryId: string, postId: string } }) {
const iconPath = path.join(process.cwd(), 'public/images/kitatai.png');
if (!fs.existsSync(iconPath)) {
throw new Error('Icon image not found');
}
const iconBuffer = fs.readFileSync(iconPath);
const post = await getPost(params.categoryId, params.postId);
if (!post || !post.frontMatter) {
throw new Error(`Post not found for categoryId: ${params.categoryId}, postId: ${params.postId}`);
}
return new ImageResponse(
(
<div
style={{
fontSize: 64,
backgroundColor: 'white',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center', // 垂直方向に中央揃え
alignItems: 'center', // 水平方向に中央揃え
position: 'relative',
fontFamily: 'Arial, sans-serif',
overflow: 'hidden',
color: 'black',
}}
>
{/* タイトル */}
<div
style={{
fontWeight: 'bold',
fontSize: '72px',
textAlign: 'center',
lineHeight: '1.2',
maxWidth: '80%', // 横幅を調整
wordWrap: 'break-word', // 必要に応じて単語を分割
overflowWrap: 'break-word', // 長い単語も折り返す
whiteSpace: 'normal', // 折り返しを有効に
}}
>
{post.frontMatter.title}
</div>
{/* 下部右にアイコンと「TAICHI KITAJIMA」 */}
<div
style={{
position: 'absolute',
bottom: '20px',
right: '20px',
display: 'flex',
alignItems: 'center',
}}
>
<img
src={`data:image/png;base64,${iconBuffer.toString('base64')}`}
alt="Icon"
style={{
width: '80px',
height: '80px',
marginRight: '10px',
borderRadius: '50%',
boxShadow: '0 4px 10px rgba(0, 0, 0, 0.2)',
}}
/>
<span
style={{
fontFamily: 'monospace',
fontWeight: 'bold',
fontSize: '48px',
}}
>
TAICHI KITAJIMA
</span>
</div>
</div>
),
{
...size,
}
);
}
こんな感じになりました。
async function handler(event) {
const request = event.request;
const uri = request.uri;
// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension.
else if (!uri.includes('.')) {
request.uri += '/index.html';
}
return request;
}
function handler(event) {
var request = event.request;
var uri = request.uri;
// Check whether the URI is missing a file name.
if (uri.endsWith('/')) {
request.uri += 'index.html';
}
// Check whether the URI is missing a file extension.
else if (!uri.includes('.') && !uri.includes('opengraph-image')) {
request.uri += '/index.html';
}
return request;
}
Next.jsのSSGでogpを動的生成している記事が少なかったので、今回の記事を書きました。また、CloudFrontでのミスも合わせて紹介しました。この記事が同じような問題に悩む人の助けになれば幸いです。