割と無茶苦茶ですが、SPA でありながらなんとか OGP 対応して GitHub-Pages でデプロイできたので奮闘を残しておきます
Nuxt2 は 2024年6月30日 に EOL を迎えました。Nuxt2 の利用、及び、この記事の内容は推奨されません
OGPは Open Graph Protcol の略で、LINEやSlack、Twitterなどでリンクを共有した際に、タイトルの他、説明や画像表示される機能 のことを指します
対応すると見栄えがよくなるので、クリックしてもらいやすくなるってことですね
ネットで調べると SSR(Server Side Rendering)の方はよく出てきますね
それもそうで、標準で対応しているからだと思います
SPA(Single Page Application) で調べようとすると SSRにしようね系の記事がヒットします
GitHub-Pages だけでなんとかしたいのと、SSR に書き換えるのも面倒なので頑張ってみます 💪
(備忘録) Netlifyの場合は Prerendering を使うともっと楽っぽい
これはとても簡単で nuxt.config.ts の head.meta に追記すればOKです
export default {head: {meta: [...{ hid: 'description', name: 'description', content: 'サイトの説明' },{ hid: 'ogTitle', property: 'og:title', content: 'サイト名' },{ hid: 'ogDescription', property: 'og:description', content: '説明' },{ hid: 'ogImage', property: 'og:image', content: 'サイトの画像URL' },...]}}
他に音楽用のタグや Twitter 用の項目が用意されていたりするので、適宜設定してください
問題はここからです
基本的な考えは、nuxt.config.ts のように、ページごとにタグを設定してあげれば良いのですが、それが一筋縄ではいってくれません
例えば以下のように vue ファイルに設定したとします
<script lang="ts">import Vue from "vue"export default Vue.extend ({head() {return {title: title,meta: [{ hid: 'description', name: 'description', content: 'ページ用の説明' },{ hid: 'ogTitle', property: 'og:title', content: 'ページ用のタイトル' },{ hid: 'ogDescription', property: 'og:description', content: 'ページ用の説明' },{ hid: 'ogImage', property: 'og:image', content: 'ページ用の画像' },],}}})</script>
しかし、この設定ではそのページのリンクを貼り付けても nuxt.config.ts で設定した内容と同じになってしまうのです
何が問題なのか?
いろいろ調べたのですが、結局 ビルド時に書き換えてしまえば良い ような気がしました
正攻法ではないと思いますが...
今回はビルド時に発火してくれる hooks という機能を使います
(この機能はいろいろ便利そうなので、もうちょっと遊びたいです)
hooks は nuxt.config.ts で呼び出すことができます
今回はレンダリングが完了したHTMLページを取得して書き換えたいので、generate.page にコールバック関数(無名関数)を登録しておきます
export default {hooks: {generate: {page: (page: any) => {page.html = rewrite_html_meta(page.html, page.route);}}},}
page.html に値を返すと、その文字列の内容で index.html に出力してくれます
rewrite_html_meta が何をしているかというと、以下のようなことをします
nuxt.config.ts の下部 (export default外) にでも貼り付けてください
const rewrite_html_meta = (html: string, origin_vue_path: string): string => {// vueパスを検索するlet vue_path = null;if (fs.existsSync(path.join('./pages', `${origin_vue_path}.vue`))) { vue_path = path.join('./pages', `${origin_vue_path}.vue`); }else if (fs.existsSync(path.join('./pages', origin_vue_path, 'index.vue'))) { vue_path = path.join('./pages', origin_vue_path, 'index.vue'); }if (!vue_path) { return html; }// ファイルを取得し、正規表現によりheadのみを取り出すconst vue_file = fs.readFileSync(vue_path, 'utf-8');let contents = vue_file.match(/head *\( *\)[ \n]*\{[^\{\}]*(\{[^\{\}]*(\{[^\{\}]*(\{[^\{\}]*[^\{\}]*\}[^\{\}]*)*[^\{\}]*\}[^\{\}]*)*[^\{\}]*\}[^\{\}]*)*[^\{\}]*\}/); // 四重入れ子まで対応if(!contents) { return html; }// meta表現を取得し、それを正規表現によって適用するlet meta: {title: string, meta: {hid: string, name: string, content?:string, property?: string}[]} = eval(`function ${contents[0]}\nhead();`);if(!meta.meta) { return html; }let result = html;let stack: string[] = [];// head用のHTML表現を作成し追加する。既存の表現があれば置換するmeta.meta.forEach((data: any)=>{data.content = data.content.replaceAll('\n', ' ');let args: string[] = [];Object.keys(data).forEach((key)=>{if(key === 'hid') { args.push(`data-hid="${data.hid}" `); result = result.replace(new RegExp(`<meta data-n-head="1" data-hid="${data.hid}".*?>`), '')}else { args.push(`${key}="${data[key]}" `); }})stack.push(`<meta data-n-head="1" ${args.join('')}>`);})if(stack.length > 0) { result = result.replace(/ <\/head>/, ` ${stack.join('')}\n <\/head>`); }return result;}
簡単に言えば元のvueファイルから head 関数を実行して、その結果を上書きするって感じです (なんともゴリ押し)
これで、OGPが認識されリッチに表示されるようになります。めでたしめでたし 🎉 (...ゴリ押しはめでたいのかな?)
ここらのメタデータの書き換えぐらいは、標準でサポートしてくれると嬉しいですねえ
それとも書き換えてはいけないヤバい理由があるのかしらん...?