本記事はOne Tech Blogに寄稿した記事を転載したものです
こんにちは GMOグローバルサイン・ホールディングス株式会社 GSP事業部 PlayCanvas推進室室長の津田@utautattaroです。
普段はPlayCanvasの事業責任者として普及促進活動をしたりWebGLのサンプルを作ったりしています。
会社では珍しく(?)フロント系のエンジニアなので、今回はフロントエンドな話題を取り上げてみました。
ついに会社のテックブログができたという話を聞いて見に行ったところ、いろいろ気になってしまったので、勝手に爆速にしたエントリです。今回はWordPress製のこのテックブログをゲリラ的にJAMStack実装して爆速にしたのでその結果と過程を残したいと思います。
Wappalyzerで確認したらこのような実装になっていました。
ふむふむ、まあ会社としてPleskを結構押し出しているし、WordPressなら拡張性も高いしいいと思います。
でもこれじゃパフォーマンスどうなんだろうと思ってLighthouseをかけてみました。
ふむ、微妙です。これは会社の技術をアピールするブログとしていかがな数値でしょう。
テックブログたるもの、Webパフォーマンスも完璧を目指すべき!ということで最近フロントエンド界隈で流行ってきているJAMStackアーキテクチャで勝手に作り直して計測比較してみたいと思います。
JAMstackとは2016年ごろにNetrifyの創設者Matt Biilmann氏が提唱した新しいフロントエンドスタックです。 リクエストを受けてからサーバー上で処理を行いレスポンスを返す従来のWebサイトの配信方法とは異なり、JAMstackでは事前にビルドされたHTMLをCDN上で配信します。[1]
旧来のWebアプリはこのようにWebページにアクセス後DBへデータを取得しに行っていました。
それがJAMStackだと以下のようになります。
エンドユーザー(※下列)はデプロイされた静的データを返却するWebサーバーにアクセスするだけなので爆速です。またDBとエンドユーザーの経路がつながっていないため、セキュリティも強固です。
ただこれだとDBが更新されてもWebサイトが古いままなのでエンドユーザーは古いデータにアクセスしてしまう恐れがあります。
ですがJAMStackアーキテクチャでは、DBが更新されるタイミングで都度デプロイをするため、Webサーバーには最新の状態を保持し続けることができるのです。[2]
つまるところユーザーが見るコンテンツは従来のようにクライアントサーバー間でアクセス時にやり取りするのではなく、事前にCI/CDでビルドして静的にホストしておこうぜという感じです。静的サイトとしてホストされるのでパフォーマンス的にもSEO的にもセキュリティ的にもばっちりとのこと。CI/CDが当たり前になってきたからこそ実現可能なアーキテクチャです。
JAMStackでは、レガシーなWebサイトと違い、継続的にCI/CDを行いデプロイを繰り返すため、以下の要素がそれぞれ必要となります。
ということで、まず結論から、今回はこのテックブログのJAMStack版コピーブログを作成しました。
https://gmocloud-adp.github.io/tech.gmogshd.com/
今回はJAMStackで移行可能な範囲のみを再構築してコピーを作成しました。
また、以下の機能は再現していません
今回は先ほど挙げた必要な要素としてそれぞれ
を選定しました。
WordPressからのJAMStack化を実現するために、まずWordPress側でWP REST APIに対応してくれていないと話になりません。
試しにWP REST APIのエンドポイントであるhttps://tech.gmogshd.com/wp-json/wp/v2/posts へアクセスしたところjsonが返ってきたので、実装を開始しました。
今回はパフォーマンスチューニングが目的だったので使い慣れたnuxt.jsを選びました。GatsbyやGridsomeを利用するとdata-sourceとしてWordPressが選べるため、ただWordPress -> JAMStack化だけしたい方はそちらの方が手軽です。
手軽さ重視で今回はGithub Pagesを選定しました。またPerformanceの項目ではキャッシュ周りやサーバー周りの数値が割と細かくチェックされるため、一度素の状態でGithub Pagesをlighthouseで計測した際にサーバー周りの項目が指摘されていなかったのも決め手です。
今回はGithub pagesにデプロイすることが決まっているためCI/CDは自ずとGithub Actionsになりました。
lighthouseはこのようになりました。圧倒的です。
記事ページはこんな感じです。
Performanceのスコアは環境によって左右されるのですが、これだけは満点行きませんでした。確認したところWordPress側で画像をWebp対応してくれれば行けそうです。
記事ページのAccessibilityはaタグの色が背景色の白とコントラスト比が少ないようで怒られました。
JAMStackサイトの作り方は、基本的に通常のStatic Site Generatorを利用した開発手法と大きく変わりません。
今回はGithubにべったり依存した形で構築するので、まずリポジトリを作ります。
リポジトリは gmocloud-adp/tech.gmogshd.com
としました。
できたらcreate-nuxt-app
していきます。このような設定でnuxt.jsアプリを作成しました。
# npx create-nuxt-app tech.gmogshd.com
create-nuxt-app v3.4.0
✨ Generating Nuxt.js project in tech.gmogshd.com
? Project name: tech.gmogshd.com
? Programming language: JavaScript
? Package manager: Npm
? UI framework: Bootstrap Vue
? Nuxt.js modules: Axios
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Static (Static/JAMStack hosting)
? Development tools: jsconfig.json (Recommended for VS Code if you're not using typescript)
? What is your GitHub username? utautattaro
? Version control system: Git
個人的にBootStrapが好きなのでBootStrapを選択しています。
WP REST APIを参照する際にaxiosが必要なのでこの場でインストールします。Rendering mode, Deploy targetはそれぞれSSR、Staticを選択します。
アプリケーションができたらgit initしておきましょう。
git init
git add .
git commit -m "first commit"
git remote add origin https://github.com/gmocloud-adp/tech.gmogshd.com.git
git push -u origin master
今回JAMStackで実装するページのルーティングは以下の通りとなります。
これをnuxt.jsのpagesディレクトリで以下のように再現しました。
> tree /f
pages
│ index.vue
│ _article.vue
│
├─category
│ _page.vue
│
└─page
_page.vue
nuxt.config.jsではこのように動的ルーティングを生成しています。nuxt.config.js
generate: {
async routes() {
const generates = [];
let categoryarray = [];
/*/postsではカテゴリーはIDで入ってくるため先にカテゴリー一覧を取得する*/
await axios.get("https://tech.gmogshd.com/wp-json/wp/v2/categories")
.then((res)=>
{
res.data.map(c =>
{
let category = {
"name" : c.name,
"slug" : c.slug,
"id" : c.id,
"articles" : []
}
categoryarray.push(category);
})
})
/*全ページ取得*/
await axios.get('https://tech.gmogshd.com/wp-json/wp/v2/posts?_embed&per_page=100')
.then((res) =>
{
res.data.map((post,index) =>
{
/*ひとつ前、一つ後の記事を取得*/
let nextArticle = "";
if(res.data[index - 1]) nextArticle = res.data[index - 1];
let previousArticle = "";
if(res.data[index + 1]) previousArticle = res.data[index + 1];
for(let i = 0;i<categoryarray.length;i++){
for(let l = 0;l<post.categories.length;l++){
if(post.categories[l] == categoryarray[i].id){
if(post["categoryobj"]){
post["categoryobj"].push(categoryarray[i]);
}else{
post["categoryobj"] = [categoryarray[i]];
}
}
}
}
for(let i = 0;i<categoryarray.length;i++){
for(let l = 0;l<post.categories.length;l++){
if(post.categories[l] == categoryarray[i].id){
let eyecatchimgurl = "https://tech.gmogshd.com/wp-content/uploads/2020/10/eyecatch-2.png";
if(post._embedded["wp:featuredmedia"]){
eyecatchimgurl = post._embedded["wp:featuredmedia"][0].media_details.sizes.full.source_url;
}
categoryarray[i].articles.push({
"date" : post.date.slice(0,10),
"modified" : post.modified.slice(0,10),
"slug" : post.slug.replace(/%.*/g,""),//日本語カノニカルURLは除外する
"title" : post.title.rendered,
"eyecatchimgurl" : eyecatchimgurl,
"content" : post.content,
"description":post.excerpt.rendered,
"categoryobj":post.categoryobj
});
}
}
}
let obj = {
route: '/' + post.slug.replace(/%.*/g,""),
payload:{post : post ,next:nextArticle,previous:previousArticle}
}
/*10記事ごとに2ページ以降を入れていく*/
if(index%10==0){
let pagenumber = parseInt(index/10 + 1,10);
let pagepath = "/page/"+pagenumber;
let pages = {
route:pagepath,
payload : {posts : res.data.slice(index,index+9),mine : pagenumber, numofpage : parseInt(res.data.length / 10 + 1,10)}
}
generates.push(pages);
}
generates.push(obj);
})
})
/*カテゴリページ作成*/
for(let i = 0;i<categoryarray.length;i++){
let obj = {
route : '/category/' + categoryarray[i].slug,
payload : categoryarray[i]
}
generates.push(obj);
}
return generates;
}
},
async asyncData({ $axios }) {}
でビルド時にすべての記事を取得して表示するように実装しました。
本家が10記事ごとにページネーションをかましていたので、同じように表示していますが、実際には全記事検索用に事前に取得しています。
検索は本家と合わせて`s`クエリに検索結果を入れたら検索画面が出るようにしました。
WordPressでは`search=`クエリを記事取得のエンドポイントに追加すれば検索となるのでシンプルにそのまま実装しています。index.vue
mounted : function(){
let self = this;
if(this.$router.currentRoute.query.s){
this.isSearch = true;
self.posts = [];
let endpoint = "https://tech.gmogshd.com/wp-json/wp/v2/posts?_embed&per_page=100&search=" + this.$router.currentRoute.query.s;
axios.get(endpoint).then((res)=>{
self.articleCount = res.data.length;
console.log(res.data);
if(res.data.length == 0){
return 0;
}
res.data.map((post,index) => {
let obj;
for(let i = 0;i<self.allposts.length;i++){
if(post.title.rendered == self.allposts[i].title){
obj = {
"title": self.allposts[i].title,
"description" : self.allposts[i].description,
"date":self.allposts[i].date,
"modified":self.allposts[i].modified,
"category":"hoge",
"eyecatchimgurl":self.allposts[i].eyecatchimgurl,
"slug":self.allposts[i].slug,
"categoryobj":self.allposts[i].categoryobj
}
}
}
self.posts.push(obj);
});
});
}
},
本家にもあったので実装しました。
WP REST APIで得られる本文はHTMLで書かれていたのですが`<h1>`から`<h5>`のタグはすべてidが振られていなかったため、idを振ってリスト化し、`vue-scrollto`でスムーススクロールするような処理を追加しました。_article.vue
async asyncData ({params, error, payload }) {
// payloadでデータを受け取った場合
if(payload){
let content = payload.post.content.rendered;
let reg = /<h[1-5].*?<\/h[1-5]>/g; /*ヘッダータグの正規表現*/
let tables = payload.post.content.rendered.match(reg);
let t = [];
if(tables){
for(let i=0;i<tables.length;i++){
let id = Math.random().toString(32).substring(2); //ランダム文字列を生成
let obj = {
"tag" : tables[i].slice(1,3),
"id" : id,
"text" : tables[i].match(/>.*</)[0].slice(1).slice(0,-1),
}
let replace = "<"+obj.tag+" id='"+obj.id+"'>"+obj.text+"</"+obj.tag+">";//ヘッダーを全て入れ替え
content = content.replace(tables[i],replace);
t.push(obj);
}
}
/*目次データを返却*/
return {
tableofcontent : t
}
}
}
vue-scrollto
はnpm install
でインストールした後pluginに記述します/plugins/vue-scrollto.js
import Vue from 'vue'
import VueScrollTo from 'vue-scrollto'
Vue.use(VueScrollTo, {
duration: 700,
easing: [0, 0, 0.1, 1],
offset: -100,
})
nuxt.config.js
での読み込みも忘れずに。
plugins: [
'~plugins/vue-scrollto'
],
最後に目次を表示してあげれば完璧です。_article.vue
<strong>目次</strong><b-button variant="outline-dark" v-b-toggle="'collapse-2'" class="m-1 px-1 py-1" style="font-size:12px; font-weight: bold;"><span v-if="!opentoc">開く</span><span v-if="opentoc">閉じる</span></b-button>
<!-- Element to collapse -->
<b-collapse id="collapse-2">
<b-list-group-item class="py-1 tablelist" v-bind:class="'t'+table.tag" v-scroll-to="'#' + table.id" v-for="table in tableofcontent" to href="#" v-html="table.text"></b-list-group-item>
</b-collapse>
Github Actionを利用するので.github/workflows/main.yml
に以下のように記載します。.github/workflows/main.yml
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
schedule:
- cron: '0 0 * * *'
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout 🛎️
uses: actions/checkout@v2
- name: setup node
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Cache dependencies
uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: install
run: npm ci
- name: test
run: npm test
- name: generate
run: npm run generate
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.2
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BRANCH: gh-pages # The branch the action should deploy to.
FOLDER: dist # The folder the action should deploy.
CLEAN: true # Automatically remove deleted files from the deploy branch
WordPressからの更新通知についていくつか考えたのですが、いったん手軽にcronでデプロイを回してみました。
これで毎日0時0分(UTC)に更新がかかるようになりました。現在のテックブログの更新頻度では十分だと思います。
lighthouseのチェックの際にPWAのバッヂもあるのでPWA化もしておきました。@nuxtjs/pwa
を利用すれば一瞬でできます。アイコンとテーマカラーを設定しないとlighthouse満点にならないので注意してください。
以上でJAMStackテックブログが構築できました!
ということで今回は勝手にWordPress -> JAMStack化できました。
0からの構築だとルーティングについてはいろいろ考慮すべき点が多く大変でした。GatsbyやGridsomeのdata-source WordPressを利用すればその辺だいぶ楽になるのかもしれません。
コメント機能や表示回数など、一部持ち込めなかった機能もありましたが、表示速度もだいぶ高速化できましたしPWA化もできたのでホームに追加もできるようになりました。
ただJAMStackも完璧ではないため、長期で運用していくには保守性やホスティング先、CI/CDの上限を考える必要がありますし、指定時間に投稿ができない、コメントの反映が即時ではないためリアルタイムのコミュニケーションには不向きといろいろ課題もあります。
それぞれの良いところ、悪いところを理解したうえで最適なアーキテクチャを選定していきたいですね。
以上!ここまで読んでいただきありがとうございました。
この企画を最初に話した際に、既存のテックブログのシステムを思いっきり否定しているのに掲載を快諾してくださったCTO室浅野さん,大西さん。大変ありがとうございました。
[1][AWSでJAMstackことはじめ(基礎知識編)]
https://www.mitsue.co.jp/knowledge/blog/frontend/201912/10_0000.html#:~:text=JAMstack%E3%81%A8%E3%81%AF2016%E5%B9%B4,%E4%B8%8A%E3%81%A7%E9%85%8D%E4%BF%A1%E3%81%97%E3%81%BE%E3%81%99%E3%80%82
[2][Jamstackって何なの?何がいいの?]
https://qiita.com/ozaki25/items/4075d03278d1fb51cc37
Among us向けヘルパーツールを作りました「Among us ビジュアルヘルパー」
2021年3月8日(月) 15時23分32秒 | 153 view埋め込み要素(iframe / embed)をいい加減対応させたい
2021年6月9日(水) 1時2分54秒 | 73 viewactions-js/push@masterでのデフォルトブランチがmasterからmainに変わっていてCIが動かなくなっていた
2021年8月30日(月) 8時49分10秒 | 71 viewtweetlogを作った
2020年10月10日(土) 3時22分55秒 | 32 viewjresult2024
2024年4月18日(木) 1時55分33秒 | 4 view