ポートフォリオサイトを Next.js + microCMS + Xserver でリニューアルしました

WordPressで運用していたこのポートフォリオサイトを、Next.js(静的書き出し)+ microCMS + Xserver静的配信の構成にリニューアルしました。この記事では、なぜこの構成にしたのか、移行で工夫した点をまとめます。このサイト自体のソースコードは GitHub で公開しています
なぜリニューアルしたか
もともとはWordPressで作っていましたが、「Next.js / TypeScript / microCMS を使える」という自分のスキルを、言葉だけでなく動いているサイトそのもので示したいと考えました。作っているサイトが、そのまま実力の証明になる状態を目指しています。
全体の構成
ビルドからデプロイまでは GitHub Actions で自動化していて、手作業でのファイルアップロードはありません。流れは次の通りです。
- microCMS に記事・実績・プロフィールを入稿して公開する
- その更新をきっかけに GitHub Actions が起動し、ビルド(
next build/output: 'export'で静的書き出し)を実行する - 続けて GitHub Actions が、書き出した静的ファイルを SSH+rsync で Xserver へ自動アップロードする
- Xserver は届いた静的ファイルを配信するだけ(サーバー側で Node.js は動かさない)
技術選定の理由
- Next.js(App Router / 静的書き出し):全ページを事前にHTML化してXserverにそのままアップできるようにするためにApp Routerで実装しました。
- microCMS:ヘッドレスCMSで記事とコードを分離できます。Hobbyプランが商用利用OKな点も選定理由のひとつです。
- Xserver(静的配信):もともと契約しているサーバーで、追加費用ゼロで配信できます。ビルドはGitHub Actions側で行い、サーバーには静的ファイルだけを置く形にしました。
- GitHub Actions:ビルドからデプロイまで自動化しました。
- パブリックリポジトリ:コード自体を見てもらえるよう、リポジトリは公開にしています。
移行で工夫したこと
- 記事のURLを維持:既存の約220記事のURL(
/blog/元のスラッグ/)をそのまま保てるよう、microCMSのコンテンツIDに元のスラッグを使い、末尾スラッシュ付きURLで書き出しています。SEOの流入を守るためです。 - 画像は移行しない:本文中の画像は Xserver の
/wp-content/uploads/をそのまま残して参照し続けています。WordPress本体を退避しても、この画像フォルダは残す方針にしました。 - 元の公開日を保持:CMSに登録し直すと登録日が新しくなってしまうため、元の公開日を別フィールドに持たせ、表示はそちらを使っています。
- X / YouTube / Instagram の埋め込みをカード化:本文にURLを貼るだけで、各サービスのカードとして描画されるようにしました。
つまずいたところ
Xserver側の「実際に公開されているフォルダ(ドキュメントルート)」の特定に少し手間取りました。FTPアカウントごとに見えるディレクトリが違うため、テストファイルを置いて実際にどこが配信されているかを確認しながら進めました。低リスクな移行方式(WordPress本体は消さず、.htaccess の優先順位だけで切り替える)にしていたため、問題が出てもすぐ元に戻せる状態で作業できたのは安心でした。
ソースは公開しています
このサイトのソースコードは GitHub で公開しています。構成や実装の詳細はこちらをご覧ください。
GitHub: shu-web-net/shu-web-portfolio-2026-09
公開後に追加した機能
サイトを公開したあと、実際に見た人が連絡を取る手段が無いことに気づき、いくつか機能を追加しました。
- お問い合わせフォーム:静的サイトなのでフォームの送信を受け取るサーバーがなく、外部サービスの比較検討をしました。Formspree・Web3Forms・formrun・フォームズ(formzu.com)を調べた結果、「自動返信メールが無料プランで使える」「広告が出ない」「フォームのHTMLを自分で自由に作れる」という条件をすべて満たしたEmailJSを採用しました。ブラウザのJavaScriptから自分のメールアカウントを使って直接送信するAPIで、問い合わせ通知と自動返信の2通のメールを送るようにしています。
- ヘッダー・フッターへの導線:お問い合わせページへのリンクと、X(Twitter)のアイコンを追加しました。
- sitemap.xml / robots.txt:Next.jsのApp Routerの規約ファイル(
app/sitemap.ts/app/robots.ts)で、記事221件を含む全ページのURLをGoogleに伝えられるようにしました。旧URLを維持した移行の効果を、検索エンジンにも正しく認識してもらうためです。 - Google Analytics:旧WordPressサイトで使っていた測定IDをそのまま引き継ぎ、リニューアル前後のアクセス推移を比較できるようにしました。
- ファビコン・OGP:ブランドに合わせたモノグラムのファビコンと、SNSでシェアされた時のカード表示(OGP)を整えました。
デプロイをFTPSからSSH+rsyncに変更した
公開後、記事を1件更新するだけのデプロイでも毎回8〜13分かかっていました。原因はXserverのFTP制御コネクションがたびたびタイムアウトすることで、タイムアウト時間を延ばしたり自動リトライを入れたりしても根本解決しませんでした。
そこでSSH+rsyncに切り替えたところ、実際のファイル転送が約6秒まで短縮されました。rsyncは内容が変わっていないファイルを自動でスキップして転送するため、記事1件の更新のような小さな変更に強い方式です。
切り替えの途中では、ローカルからは同じSSH鍵で問題なく接続できるのに、GitHub Actionsからだけ接続が拒否されるという現象にも遭遇しました。原因はXserverのSSH設定が「国内からのアクセスのみ許可」になっていたためで、GitHub Actionsは海外のサーバーで実行されるため弾かれていました。公開鍵認証のみでパスワードログインは不可なので、アクセス許可を「すべて許可」に変更しても実質的なリスクは小さいと判断し、設定を変更して解決しました。
技術メモ(Notion)
実装で調べたこと・理解したことを、トピックごとに Notion にまとめています。いずれも「サーバーを持たない静的サイト(output: 'export')」という制約の中で、どう成立させるかという視点で整理しています。