投稿日:
更新日:

Orenge Diary をリビルドしました

Authors

目次

banner.png

はじめに

3 年ぶりに Orenge Diary をフルスクラッチリニューアルしました。

これまで、jQuery と PHP をベースに Nginx on KVM(Ubuntu)でセルフホスティングしていましたが、モダンな Web フロントアーキテクチャを目指して Next.js へリビルドし、Vercel に移設しました。

今回のブログではポートフォリオサイトのリビルドについて軽く紹介したいと思います。

ロードマップ

不得手なデザインから取り掛かり、約 4 ヶ月かけてフルスクラッチで書き直した。

  • 2022 年 11 月:基礎レイアウトの再設計

久しぶりに、ちゃんと Figma を触った。

figma-design.png
  • 2022 年 12 月:技術選定と既存コンポーネントの切り出し

アーキテクチャ構成と、各種アセット管理の移行先を机上検討。

ren510dev-repository.png
  • 2023 年 1 月:ひたすらコーディング作業

とりあえず時間さえあればひたすらコーディング。

メインフレームの移行作業をだいたい 1 ヶ月で終えた。

  • 2023 年 2 月:ひたすらコーディング作業

レイアウト調整とメンテナンス関連のツールを導入した。

Todo リスト / Issue の解消とエラーリファクタリングがメイン。 その他、パッケージのバージョン管理と DevOps ができるように周辺環境を整えた。

  • 2023 年 3 月:公開

Next.js を Vercel へデプロイした。

orenge-diary-vercel-app.png

公開後に OGP / SEO / ポータビリティ 等、諸々の動作検証を実施した。

orenge-diary-published.png

ロゴとアイコンの追加

より WEB サイトっぽくするために、ヘッダロゴと favicon を作ってみた。

  • ヘッダと OGP 用のロゴ
orenge-diary-logo.png
  • favicon 用 ゆるキャラ "Renge くん 🍊"
orenge-diary-favicon.png
  • テーマカラーは変わらず Bordeaux #220000

平手友梨奈さんが、ガラスを割れ の MV で着ていたフライトジャケットがカッコ良いんだ!

theme-color.jpg

以前のアーキテクチャ

legacy-architecture.png

KVM + Nginx

nginx-logo.png

元々、この WEB サイトは自宅サーバで運用しており、Ubuntu ベースのマシンを KVM(Kernel-based Virtual Machine) で仮想化して、その上に Nginx を立ててホスティングしていた。

温かみのある W3C

basic-web-stack-logo.png

WEB コンテンツも、オーソドックスな HTML5 + CSS3 + JavaScript でコーディングしていて、静的コンテンツは Nginx で配信、ブログ(Markdown)や WEB サイト内の動的なデータ(JSON)は、リクエストに応じて FastCGI でバックエンドのコンポーネントをコールする。

フロントエンドの FW には CSS に Bootstrap を、JavaScript には jQuery(jQuery Plugin)を使用し、バックエンドは PHP(モジュール管理に Composer)で書いていた。

php-logo.png

フロントエンドとバックエンドの通信はシンプルに Ajax で。

$(document).ready(function () {
  // 省略

  function loadArticles() {
    $.getJSON('/api/list.php', function (data) {
      $('#article-list').empty()
      data.articles.forEach((article) => {
        let link = $('<a>').attr('href', `?file=${article.file}`).text(article.title)
        link.click(function (event) {
          event.preventDefault()
          loadMarkdown(article.file)
        })
        $('#article-list').append($('<li>').append(link))
      })
    })
  }
})
<?php
require __DIR__ . '/vendor/autoload.php';

use Ren510dev\MarkdownBlog\MarkdownParser;

header("Content-Type: application/json; charset=UTF-8");

$filename = isset($_GET['file']) ? basename($_GET['file']) : '';
$filePath = "blog/$filename";

if (!$filename || !file_exists($filePath)) {
  echo json_encode(["error" => "File not found"]);
  exit;
}

$markdownContent = file_get_contents($filePath);
$html = MarkdownParser::parse($markdownContent);

echo json_encode(["html" => $html]);

// 省略

ユーザリクエスト

legacy-architecture-network.png

パブリックネットワークからのリクエストは DMZ(DeMilitarized Zone) で隔離した LAN に入った後、Nginx リバースプロキシを通じて WEB サーバへ流す。

WEB サイトのドメインや証明書の管理には、Google DomainsLet's Encrypt + Certbot を使用していた。

リビルドの背景

これまでは WEB サイトやブログの更新は、サーバ内のファイルを直接編集した後、PHP-FPM を通じて動的にレンダリングして反映させていた。

また、サーバと GitHub の連携等はされていない上、CI/CD についても考慮しておらず、メンテナンス作業の辛みや旧石器アーキテクチャから脱するべく、モダンな WEB フレームワークへの移設に踏み切った。

移行後のアーキテクチャ

replaced-architecture.png

Next.js + TypeScript

WEB フロントアーキテクチャのモダナイゼーションを目指し、JavaScript FW として Next.js を使用する。

next-js-logo.png

実装には、コンポーネントの明示的なクラス化や型制約を持たせるために TypeScript を使用する。

typescript-logo.png

以前は、リクエストに応じてコンテンツを都度 Ajax で呼び出していたが、SSG(Static Site Generation) ができるので、直接フロント側のコードにブログ(MD → MDX)や WEB サイトデータ(JSON → YAML)もオールインワンで追加した。

build-check.png

また、スタイリングには少ないコード量で CSS が書けて、レスポンシブ対応等もしやすいという観点から TailwindPrism を使用した。

tailwind-logo.pngprism-logo.png

YAML ベースの管理

js-yaml-loader.png

テンプレートに挿入する情報は YAML で管理することにした。 これにより、Next.js 側のコードを触ることなく、単に MDX や YAML を編集して GitHub に Push するだけで、ブログや WEB サイトの基本的な更新ができるのでかなり運用が楽になった。

### ./data/topics/data.yaml ###
- category: 'Blog'
  tags:
    - 'orenge-diary'
    - 'rebuild'
    - 'next-js'
    - 'vercel'
  thumbnail: '<Cloudinary で管理している 画像パス>'
  url: '/blog/orenge-diary-renewal'
/* next.config.js */
module.exports = withBundleAnalyzer({
  webpack: (config, { dev, isServer }) => {
    // 省略

    config.module.rules.push({
      test: /\.yaml$/,
      use: ['yaml-loader'],
    })

    return config
  },
})
import React from 'react'
import Link from '@/components/Link'
import Hashtag from '@/components/Hashtag'
import data from '@/data/topics/data.yaml'

const noImagePath = '/static/general/no-image.png'
const topicsData: Topic[] = data.topics

interface Topic {
  category: string
  tags: string[]
  thumbnail: string
  summary: string
  url: string
}

const todayDate = new Date().toLocaleDateString('ja-JP', {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
})

export default function RecentArticles() {
  return (
    <div className="rounded-md border border-solid bg-opacity-5">
      {topicsData.map((topic, index) => {
        const { category, tags, thumbnail, url } = topic
        const impression = thumbnail !== '' ? thumbnail : noImagePath
        const isLast = index === topicsData.length - 1
        return <React.Fragment key={index}>{/* 省略 */}</React.Fragment>
      })}
    </div>
  )
}

Pages Router + SSR

next-js-ssr.png

各セクションのフレームは Next.js Pages Router で管理して、ページ毎に SSR(Server-Side Rendering) でコンテンツをレンダリングする。

CDN + HTTP/3 対応

画像データの管理は Cloudinary で行い、各コンテンツは参照先の情報のみを定義する。 これにより、従来ローカルファイルとして管理していたデータも、CDN で配信できるようにした。

ドメインは Cloudflare へ移管して HTTP/3 にも対応した。

cloudinary-cdn.png
cloudflare-http3.png

PWA 対応

PWA(Progressive Web Apps) に対応することで、ネイティブアプリケーションとして配布できるようにした。

pwa-console.png
macos-app-installation.png
ios-app.png
macos-app.png

CI/CD

コードを変更した後は GitHub に Push することで CI Workflow を発火させ、pre-commithusky で品質を担保する。

#!/bin/sh

if [ -z "$husky_skip_init" ]; then
  debug() {
    [ "$HUSKY_DEBUG" = "1" ] && echo "husky (debug) - $1"
  }

  readonly hook_name="$(basename "$0")"
  debug "starting $hook_name..."

  if [ "$HUSKY" = "0" ]; then
    debug "HUSKY env variable is set to 0, skipping hook"
    exit 0
  fi

  if [ -f ~/.huskyrc ]; then
    debug "sourcing ~/.huskyrc"
    . ~/.huskyrc
  fi

  export readonly husky_skip_init=1
  sh -e "$0" "$@"
  exitCode="$?"

  if [ $exitCode != 0 ]; then
    echo "husky - $hook_name hook exited with code $exitCode (error)"
    exit $exitCode
  fi

  exit 0
fi
husky-check.png

CD サイドは Vercel に一任して、webpack バンドラを用いてビルドする。

vercel-deployment.png

まとめ

今回のブログでは、本 WEB サイトのリビルドについて紹介しました。

普段のシステムプログラミングから離れて、久しぶりに Next.js や TypeScript を触りましたが、FW が充実しており、スムーズに移行作業を進めることができました。 何より書いていて楽しかった...!

ようやく令和の時代にふわさしいアーキテクチャに近づけることができました。

そして、これを機に自宅サーバとお別れしました 👋