Orenge Diary をリビルドしました
- Authors
- Name
- ごとれん
- X
- @ren510dev
目次

はじめに
3 年ぶりに Orenge Diary をフルスクラッチリニューアルしました。
これまで、jQuery と PHP をベースに Nginx on KVM(Ubuntu)でセルフホスティングしていましたが、モダンな Web フロントアーキテクチャを目指して Next.js へリビルドし、Vercel に移設しました。
今回のブログではポートフォリオサイトのリビルドについて軽く紹介したいと思います。
ロードマップ
不得手なデザインから取り掛かり、約 4 ヶ月かけてフルスクラッチで書き直した。
- 2022 年 11 月:基礎レイアウトの再設計
久しぶりに、ちゃんと Figma を触った。

- 2022 年 12 月:技術選定と既存コンポーネントの切り出し
アーキテクチャ構成と、各種アセット管理の移行先を机上検討。

- 2023 年 1 月:ひたすらコーディング作業
とりあえず時間さえあればひたすらコーディング。
メインフレームの移行作業をだいたい 1 ヶ月で終えた。
- 2023 年 2 月:ひたすらコーディング作業
レイアウト調整とメンテナンス関連のツールを導入した。
Todo リスト / Issue の解消とエラーリファクタリングがメイン。 その他、パッケージのバージョン管理と DevOps ができるように周辺環境を整えた。
- 2023 年 3 月:公開
Next.js を Vercel へデプロイした。

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

ロゴとアイコンの追加
より WEB サイトっぽくするために、ヘッダロゴと favicon を作ってみた。
- ヘッダと OGP 用のロゴ

- favicon 用 ゆるキャラ "Renge くん 🍊"

- テーマカラーは変わらず Bordeaux
#220000
平手友梨奈さんが、ガラスを割れ の MV で着ていたフライトジャケットがカッコ良いんだ!

以前のアーキテクチャ

KVM + Nginx

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

WEB コンテンツも、オーソドックスな HTML5 + CSS3 + JavaScript でコーディングしていて、静的コンテンツは Nginx で配信、ブログ(Markdown)や WEB サイト内の動的なデータ(JSON)は、リクエストに応じて FastCGI でバックエンドのコンポーネントをコールする。
フロントエンドの FW には CSS に Bootstrap を、JavaScript には jQuery(jQuery Plugin)を使用し、バックエンドは PHP(モジュール管理に Composer)で書いていた。

フロントエンドとバックエンドの通信はシンプルに 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]);
// 省略
ユーザリクエスト

パブリックネットワークからのリクエストは DMZ(DeMilitarized Zone) で隔離した LAN に入った後、Nginx リバースプロキシを通じて WEB サーバへ流す。
WEB サイトのドメインや証明書の管理には、Google Domains と Let's Encrypt + Certbot を使用していた。
リビルドの背景
これまでは WEB サイトやブログの更新は、サーバ内のファイルを直接編集した後、PHP-FPM を通じて動的にレンダリングして反映させていた。
また、サーバと GitHub の連携等はされていない上、CI/CD についても考慮しておらず、メンテナンス作業の辛みや旧石器アーキテクチャから脱するべく、モダンな WEB フレームワークへの移設に踏み切った。
移行後のアーキテクチャ

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

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

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

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

テンプレートに挿入する情報は 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 Pages Router で管理して、ページ毎に SSR(Server-Side Rendering) でコンテンツをレンダリングする。
CDN + HTTP/3 対応
画像データの管理は Cloudinary で行い、各コンテンツは参照先の情報のみを定義する。 これにより、従来ローカルファイルとして管理していたデータも、CDN で配信できるようにした。
ドメインは Cloudflare へ移管して HTTP/3 にも対応した。
PWA 対応
PWA(Progressive Web Apps) に対応することで、ネイティブアプリケーションとして配布できるようにした。
CI/CD
コードを変更した後は GitHub に Push することで CI Workflow を発火させ、pre-commit と husky で品質を担保する。
#!/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

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

まとめ
今回のブログでは、本 WEB サイトのリビルドについて紹介しました。
普段のシステムプログラミングから離れて、久しぶりに Next.js や TypeScript を触りましたが、FW が充実しており、スムーズに移行作業を進めることができました。 何より書いていて楽しかった...!
ようやく令和の時代にふわさしいアーキテクチャに近づけることができました。
そして、これを機に自宅サーバとお別れしました 👋