Yewの現状
Yewは一般的なSPAと同様に動作します。そのような多くのフロントエンドフレームワークでは、SSRや動的にMetaタグを変更してくれるライブラリ等が実装されています。
しかし新参者のYewにはそのようなものはありません。そのため、自ら実装する必要がありました。
動的Metaタグの実装
まずはRoute
が変化した際に、TitleやDescription等のhead
タグ内の要素を変更するようにしました。
具体的には以下のコードのように、Document::query_selector
メソッドでmeta[name='?????']
のような引数を与え、特定のメタタグを取得します。後は状況に応じて、そのメタタグのcontent
アトリビュートを変更したり、生成・削除したりしています。
let document = yew::utils::document();
let selectors: String = format!("meta[{}='{}']", name, value);
if let Ok(Some(meta)) = document.query_selector(&selectors) {
meta.set_attribute("content", content)?;
} else {
document.create_meta(name, value, content)?;
let meta = document.create_element("meta")?;
meta.set_attribute(name, value)?;
meta.set_attribute("content", content)?;
document.head().unwrap().append_child(meta.as_ref())?;
}
OGPやMetaタグを読み込んでくれない
実際にデベロッパーツールでElementsを確認すると、ページが変わる際に変更されているのを確認できました。これでOGPやその他のメタタグが動的に変更されるようになりました。
OGP
Twitter等のSNSでは、自身で投稿した情報に含まれているURLの移行先のOGPを設定しておくと、URLの見栄えをよくしてくれるカードの機能などがあります。
以下の画像が実装して、期待する結果です。
しかし先程の実装方法では、カードなどが表示されません。どうやらサイト内のmeta
タグ等の情報を取得する多くのボットでは、最初に読み込まれるHTMLファイルのhead
タグからのみ取得するらしいです。
このような仕様により、後のjavaScriptファイル等による動的に生成・変更されたmeta
タグは読み取ってくれません。
検索クローラー
Googleの検索クローラーでは、JavaScriptやWASM等を実行し、その情報も読み取ってくれるらしいです。このことがあったため、最悪OGPの機能が使えなくても、あまり問題ないと思っていました。
しかし、Googleコンソールで確認してみると、上手く読み込んでいないことが判明しました。ある記事では時間が経たないと、JavaScriptファイルを取得し切れていないとあったが、数日経っても変わりませんでした。
このままだと検索結果に表示されず、サイトが見られないので、流石に対処しなければ、ということになりました。
SSR
サーバー側でJavaScript等の実行を行い、実行後の結果をクライアント側に送るのが、SSRです。主なメリットとして、クライアント側での初期実行の処理時間が減少し、サイトを開いてから表示される時間が減少します。
SSRにより、meta
タグもクライアントに送られた段階で生成・変更されるので、どのクローラーもmeta
情報を取得することができます。
YewでもSSRを実装する試みがあるのですが、あまり活発ではなく、現状ではありません。
Prerendering
SSRと似たように、サーバー側で事前にJavaScript等の実行を行います。違いとしては、Prerenderingの場合、HTMLのような静的ファイルを作成します。(認識が間違っているかもしれないので、そのときはご連絡していただけたらと思います)
Prerenderingの場合は、Yewのようなフレームワークに依存しません。そのためwebpack系のライブラリを探していると、PrerenderSPAPluginというものを見つけました。他にもPrerenderingするライブラリ等があったのですが、実装のしやすさ的に、このライブラリにすることを決めました。
実装
PrerenderSPAPlugin
webpack.json
の内容をいじると、指定したroutes
のHTMLファイルをそれぞれ生成してくれます。
具体的には、このような感じにしました。
new PrerenderSPAPlugin({
staticDir: distPath,
routes: [
'/',
// ...
'/tumple',
],
postProcessHtml: function (context) {
return context.html.replace(
/http:\/\/localhost:8000/gi, 'https://bkbkb.net'
)
},
renderer: new Renderer({
renderAfterDocumentEvent: 'prerender-trigger',
})
})
生成する際は、ローカル環境のドメインになるので、URLを置換するようにしました。(自分は失敗しましたが、path
か何かで上手く設定すると、ドメインを変更してくれるかもしれません)
何も指定しないと、プリレンダリングの際にJavaScript等がまだ実行していないときに、HTMLが生成されてしまいました。そのためrenderer
の設定で、イベントをトリガーとして、meta
タグを変更した後に発生させて、そのあとにHTMLが生成されるようにしました。
Nginx
これでプリレンダリングが完了したのですが、生成されたHTMLは静的なためJavaScript等を含んでおらず、動的な処理はできず、本来のサイトの動作をしなくなりました。
動的な処理はしたいため、ボットの場合プリレンダリングで生成されたHTMLを送信し、一般ユーザーの場合JavaScript等を含む元のファイルを送信するようにできないかと考えました。
Nginxで$http_user_agent
ごとに送信するファイルを分ければ、できそうであることがわかりました。慣れてない.conf
ファイルの設定を変更し、ボットである場合、プリレンダリングで生成されたHTMLを送信するようにしました。
set $html_file "/main.html";
if ($http_user_agent ~* "bot|crawler") {
set $html_file "$uri/";
}
try_files $uri $html_file;
まとめ
発展途上のフレームワークを使用すると、成熟したフレームワークで実装されているものも自ら用意しなければならないことを、身をもって感じました。