Nuxt(SPA)+FirebaseでSEO!OGP!: 特定のパスだけheadだけ返すやつ

最近つくった積読ハウマッチをNuxtのSPAで作成しているけど、
シェアされたときにいい感じに画像とかを表示してほしいのでやってみた。

N番煎じ感がつよいけれど、自分の整理用〜

全体の流れ

  1. 該当のURLにアクセスがあったらリライトでFunctionを呼び出す(Hostingのrewrite)
  2. FunctionでヘッダだけのHTMLを生成。ボディには仮のパスへリダイレクト(Function)
  3. HTMLのリダイレクト先をさらにNuxt側で正しいパスリダイレクト(nuxt.config.ts)

若干複雑...

図的にはこんな感じ

スクリーンショット 2019-08-07 12.08.48.png

Function側のコード(index.js)

まずは、Cloud Function for Firebaseから。
リスエストのパスに応じてDBの値を取得して、OPG用のHTMLを生成。

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();

const db = admin.firestore();

// ********************************************************
// * Generate OPG HEAD
// ********************************************************
/**
 * OGP用のヘッダだけのHTMLを返す関数
 * @param {String} TITLE タイトル
 * @param {String} DESCRIPTION ディスクリプション
 * @param {String} OGP_URL OGP画像のURL
 * @param {String} PAGE_URL 該当ページのURL
 * @param {String} REDIRECT_URL リダイレクト先のURL
 */
const createHtml = (TITLE, DESCRIPTION, OGP_URL, PAGE_URL, REDIRECT_URL) => {
  return `<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>${TITLE}</title>
    <meta property="og:title" content="${TITLE}">
    <meta property="og:image" content="${OGP_URL}">
    <meta property="og:description" content="${DESCRIPTION}">
    <meta property="og:url" content="${PAGE_URL}">
    <meta property="og:type" content="article">
    <meta property="og:site_name" content="${SITE_NAME}">
    <meta name="twitter:site" content="${BASE_URL}">
    <meta name="twitter:card" content="summary_large_image">
    <meta name="twitter:title" content="${TITLE}">
    <meta name="twitter:image" content="${OGP_URL}">
    <meta name="twitter:description" content="${DESCRIPTION}">
  </head>
  <body>
    <script type="text/javascript">window.location="${REDIRECT_URL}";</script>
  </body>
</html>
`;
};

/**
 * '/user/<userId>'に対応するHTMLを返すFunction
 */
const BASE_URL = '<サイトのBASE_URL>'
exports.users = functions.https.onRequest(async (req, res) => {
  try {
    // PATHからパスパラメータを取得
    const [, , userId] = req.path.split("/");
    if (!userId) throw new Error(`userId is empty`);

    // パスパラメータを使って、DBからデータを取得
    const docRef = db.collection("users").doc(userId);
    const snap = await docRef.get();
    if (!snap.exists) throw new Error(`Not Found: userId=${userId}`);

    // DBのデータからHTML作成に必要なデータを用意
    const user = snap.data();
    const title = `${user.name}さんのページ`;
    const desc = `${user.name}さんのページの詳細です`;
    const ogpURL = "<該当ユーザのOGP画像のURL>";
    const pageURL = `${BASE_URL}/user/${userId}`;
    const redirectURL = `/_user/${userId}`;

    // ヘッダだけのHTMLを生成
    const html = createHtml(title, desc, ogpURL, pageURL, redirectURL);

    // キャッシュを設定
    res.set("Cache-Control", "public, max-age=600, s-maxage=600");

    // 生成したHTMLを返却
    res.status(200).end(html);
  } catch (err) {
    // エラーが発生したら'/'にリダイレクト
    console.warn(err);
    res.redirect("/");
  }
});

firebase.jsonの設定

firebase.jsonの設定。該当のパスにアクセスされたら、
リライトでFunctionを呼び出すように設定を追加。

{
  "functions": {
    "source": "functions"
  },
  "hosting": {
    "rewrites": [
      // '/user/<userId>'へのアクセスがあったらFunctionsのusersを呼び出す
      {
        "source": "/user/*",
        "function": "users"
      },
      {
        "source": "**",
        "destination": "/404.html"
      }
    ],
  },
}

nuxt.config.tsの設定

nuxt.config.jsのrouterの設定。HTML内でリダイレクトされる先を、
さらにrouter側でリダイレクト。もとに戻す感じに。

const config: NuxtConfiguration = {

  /*
   ** Router configuration
   */
  router: {
    extendRoutes(routes: NuxtRouteConfig[], resolve) {
      routes.push({
        path: "/_user/:uid",
        redirect: "/user/:uid",
        chunkNames: {}
      });
    }
  },
}

注意!! 動的パラメタのあるパスだけ使えます

ちなみに、該当のパスにHostingのHTMLがあるとダメ...
Hostingの優先度がこんな感じ...

  1. 予約済み名前空間(/__*)
  2. リダイレクトの構成
  3. 正確に一致する静的コンテンツ
  4. リライトの構成

リライトよりも静的コンテンツのほうが優先度が高いので、
リライトでFunctionを呼び出されるよりも先にHTMLが返されてしまう...

nuxt generateするとHTMLが配置されてしまうので、
動的パラメタじゃないとダメかも...

以上!!

【PR】積読ハウマッチをリリースしました!

積んでいる本の総額がわかる読書管理サービス
積読ハウマッチ』をリリースしました♪

積読が多い方も、少ない方も、ない方も、
ぜひお試しください(´ω`)

参考にしたサイト様

Originally published at www.memory-lovers.blog

きらぷか@i18n補助ツール『トランスノート』開発者

フリーエンジニア/今はNuxt.js/いつかFlutter 受託&アプリ/Webサービス/ゲームを #個人開発 CS修士→SIer/R&D→フリー #paiza はAランクで満足/AtCoderしたい 仕事依頼やご相談はDMまで Kotlin/Python/Swift/Unity/Java/Haskell/DDD

Crieitは個人で開発中です。 興味がある方は是非記事の投稿をお願いします! どんな軽い内容でも嬉しいです。
なぜCrieitを作ろうと思ったか

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください!

ボードとは?

関連記事

コメント