はじめに
SNSでシェアされた際のリンクプレビューは、ユーザーの目を引き、クリック率を高める重要な要素です。このプレビューはOGP(Open Graph Protocol)によって制御されますが、認証が必要なWebアプリケーションでは実装が難しい課題があります。特にLINEのLIFFアプリケーションではさらに複雑になります。
今回は、以下の技術スタックを使用してLIFFベースのプロダクトに動的なOGPを実装した方法を紹介します。
- LIFF(LINE Front-end Framework)
- Next.js + Supabase
- Cloudflare(Next.jsをWorker and Pagesにデプロイ)
技術スタックについて
LIFF(LINE Front-end Framework)
LIFFはLINEが提供するウェブアプリケーションプラットフォームで、LINEのユーザーインターフェースの中でウェブコンテンツを表示できます。LINEのユーザー認証を活用でき、LINEのAPIにもアクセスできる便利なフレームワークです。
LIFF公式ドキュメント
Next.js + Supabase
Reactベースのフレームワークで、サーバーサイドレンダリング(SSR)、静的サイト生成(SSG)など多くの機能を提供します。今回はメタデータ生成機能も活用しています。
Next.js公式サイト
LIFFとNext.js, Supabaseでのログインレス認証方法
Cloudflare
Cloudflareは高速でセキュアなコンテンツ配信ネットワークを提供するサービスです。特に「Cloudflare Pages」と「Cloudflare Workers」は低コストで高性能なホスティング環境を実現できます。個人開発において非常にコスパが良く、今回の実装の鍵となりました。
Cloudflare Pages
OGPとは?そして認証サービスでの課題
OGP(Open Graph Protocol)はFacebookが開発したプロトコルで、ウェブページがソーシャルグラフオブジェクトとして機能するためのメタデータを提供します。簡単に言えば、SNSでリンクをシェアした際に表示されるプレビュー(タイトル、説明文、画像など)をコントロールするための仕組みです。
Open Graph Protocol公式サイト
通常、Next.jsではgenerateMetadata
関数を使用して動的なOGPメタデータを生成できます。しかし、認証が必要なサービス(今回のLIFF + Supabaseベースのアプリケーション)では問題が発生します。
認証前提のサービスでは、OGPクローラー(LINEやTwitterなどのボット)がアクセスしたときに、認証がないため正しいメタデータを取得できません。私の場合、Supabaseの認証を使用しており、認証されていないリクエストはログインページにリダイレクトされてしまいます。そのため、単純にgenerateMetadata
を使うだけでは動的なOGPを実現できません。
試行錯誤:失敗した方法
方法1:Cloudflare Functionsを使用する
最初に試したのは、Cloudflare Functionsを使用してOGPクローラーのアクセスを検知し、Next.jsアプリケーションにアクセスが来る前にOGPを生成する方法です。
参考記事:Cloudflare Pages Functionsで動的OGP画像を生成する方法
しかし、Next.jsをnext-on-pages
を使用してWorkerに対応するように変換すると、Functionsのコードが消えて動作しなくなってしまいました。
方法2:Next.jsのミドルウェアを使用する
次に試したのは、Next.jsのミドルウェアでOGPボットのアクセスを検知し、認証不要のページにリダイレクトさせてそこでgenerateMetadata
関数を実行する方法です。
ローカル環境では上手く動作しましたが、Cloudflareにデプロイするとうまく動作せず、LINEのOGPボットがアクセスできなくなりました。エラーメッセージは「TypeError: Request with a GET or HEAD method cannot have a body.」でした。
調査の結果、これはNext.jsのミドルウェアに到達する前の段階でCloudflare側でエラーが発生していることが分かりました。
解決策:Worker Routesを活用する
最終的に成功した方法は、Cloudflare Worker Routesを使用する方法です。以下が実装手順です。
- Cloudflareでドメインを購入
- Next.jsアプリケーションをCloudflare Pages and Workersにデプロイ
- 2のCloudflare Pages and Workersを1で購入したドメインに接続
- 新しいWorkerを作成し、OGPクローラー検出用のコードを実装
- Worker Routesで設定
以下が実装したWorkerのコアコードです:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request, event))
})
async function handleRequest(request, event) {
const userAgent = request.headers.get('User-Agent') || '';
// OGPクローラーかどうかをUser-Agentで判定
const isOgpCrawler = [
"facebookexternalhit",
"line-poker",
"Facebot",
"Twitterbot",
"LineBot",
"LinkedInBot",
"Slackbot",
"TelegramBot",
"Discordbot",
"WhatsApp",
"GoogleImageProxy",
].some((bot) => userAgent.includes(bot));
if (isOgpCrawler) {
// URLからIDを抽出
const url = new URL(request.url);
const id = url.pathname.split('/').pop();
try {
// OGPデータを取得
const ogpApiUrl = `https://your-api-endpoint.com/api/public/ogp/${id}`;
const res = await fetch(ogpApiUrl);
if (!res.ok) {
throw new Error(`API returned ${res.status} ${res.statusText}`);
}
const ogpData = await res.json();
// デフォルト値の設定
const defaultTitle = "アプリケーション名";
const defaultDescription = "アプリケーションの説明";
const title = ogpData?.title ? `${ogpData.title} | アプリケーション名` : defaultTitle;
const description = ogpData?.description || defaultDescription;
const fullImageUrl = ogpData?.imageUrl ? `https://your-image-domain.com/${ogpData.imageUrl}` : null;
// OGPメタタグを含むHTMLを生成
const html = `
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>${title}</title>
<meta property="og:title" content="${title}" />
<meta property="og:description" content="${description}" />
${fullImageUrl ? `<meta property="og:image" content="${fullImageUrl}" />
<meta property="og:image:width" content="1024" />
<meta property="og:image:height" content="1024" />
<meta property="og:image:alt" content="${title}" />` : ''}
<meta property="og:url" content="${request.url}" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="${title}" />
<meta name="twitter:description" content="${description}" />
${fullImageUrl ? `<meta name="twitter:image" content="${fullImageUrl}" />` : ''}
</head>
<body>
<!-- 必要に応じてコンテンツを追加 -->
</body>
</html>
`;
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
} catch (error) {
console.error("OGP generation error:", error);
return fetch(request); // エラー時はNext.jsからのレスポンスをそのまま返す
}
} else {
// OGPクローラーでない場合は、Next.jsアプリケーションにリクエストを転送
return fetch(request);
}
}
このWorkerを設定した後、Cloudflareの「Worker Routes」セクションで以下のようにルーティングを設定します:
example.com/*
これにより、あらゆるリクエストがまずこのWorkerを通過し、OGPクローラーからのアクセスのみ特別な処理を行い、その他のアクセスは通常通りNext.jsアプリケーションに転送されます。
まとめ
Cloudflareの柔軟な機能を活用することで、認証が必要なLIFFベースのアプリケーションでも動的なOGPを実装することができました。
特に注目すべき点は、Cloudflareを使用することで、Next.jsアプリケーションにアクセスが到達する前に処理を挟むことができる点です。これにより、認証の壁を回避しつつ、適切なOGPメタタグを提供することが可能になりました。
Cloudflareは無料プランでも多くの機能が利用でき、ドメイン購入と組み合わせることでさらに可能性が広がります。個人開発においてコストを抑えつつセキュアな環境を構築できる点は非常に魅力的です。
今回の実装を通じて、フロントエンドとインフラの境界線での工夫がユーザー体験を大きく向上させることを実感しました。みなさんも是非Cloudflareの柔軟な機能を活用して、より良いWebアプリケーションを構築してみてください!
実際のアプリケーション例
本記事で解説した認証フローを実装した実例として、私が開発している「cookLab」をご紹介します。
「cookLab」は、料理好きの方々が簡単にレシピを共有し、お気に入りのレシピを保存できるLINE Bot + LIFFアプリです。
主な機能:
- レシピの検索、解析
- レシピの相談
- 盛り付け例の画像提案
- セール品で作れるレシピの提案
等、料理を楽しめる様々な機能があります。
以下のQRコードまたはリンクから、LINEの公式アカウントを友だち追加して、実際の認証フローと機能を体験してみてください!
ぜひ以下のLINE公式アカウントを友だち追加して、実際の認証フローを体験してみてください!