2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIと二人三脚で平成個人サイトを令和に蘇らせたら1500行のReactスパゲッティが爆誕した話

2
Posted at

はじめに

ある日、ふと思った。

「個人サイトって今の技術で作ったらどうなるんだろう」

最初はお遊びのつもりだったが、気づいたら1500行のスパゲッティコードと、十数個のイースターエッグが生えていた。しかもTypeScriptのビルドは通っている。

https://www.ena-dri.dev

この記事では、Claude・GeminiといったAIと協力しながら平成個人サイトを令和技術で再現した過程と、実装の裏側を紹介する。


技術スタック

React + TypeScript(Vite)
ホスティング:Vercel(無料)
DB:Supabase(アクセスカウンター用)
ドメイン:Cloudflare Registrar($12.20/年)
外部UIライブラリ:なし

外部UIライブラリは一切使っていない。

Win98のベベルボタンも、タイトルバーの青グラデも、全部インラインスタイルで書いた。

const Win98Button = ({ children, onClick, style = {} }: {
  children: ReactNode;
  onClick?: () => void;
  style?: React.CSSProperties
}) => (
  <button
    style={{
      background: "#c0c0c0",
      border: "2px solid",
      borderColor: "#ffffff #808080 #808080 #ffffff",
      padding: "4px 12px",
      fontFamily: "'MS Sans Serif', Arial, sans-serif",
      fontSize: "11px",
      cursor: "pointer",
    }}
    onMouseDown={(e) => (e.currentTarget.style.borderColor = "#808080 #ffffff #ffffff #808080")}
    onMouseUp={(e) => (e.currentTarget.style.borderColor = "#ffffff #808080 #808080 #ffffff")}
  >
    {children}
  </button>
);

AI分業体制

このプロジェクトはほぼAIとの共同作業だ。

担当 役割
Claude 設計・レビュー・壁打ち
Gemini アイデア発散・実装(Antigravity経由)
えなどり 意思決定・$12/年を払う人

Geminiには「平成初期の個人サイトのあるあるを発散してくれ」と投げ続けた。
俺の青春は、ニコニコ動画だったので。

Claudeには実装の設計レビューと、ハマったときの壁打ち相手になってもらった。


インフラ構成

ドメイン(Cloudflare Registrar)

Cloudflare Registrarはドメインを原価で売ってくれる。ena-dri.dev が$12.20/年。お名前.comより安い。

DNSの設定は1点だけ注意が必要だった。

Vercelにカスタムドメインを設定する場合、
CloudflareのプロキシをOFF(DNS only)にしないと
「Proxy Detected」エラーになる。

オレンジの雲マークをグレーにする、これだけ。

Vercel × GitHub

masterブランチにpushすると自動デプロイされる。

ひとつハマりポイントがあった。gitのメールアドレスとVercelアカウントのメールが一致していないとデプロイがブロックされる。

# 確認
git config user.email
 
# GitHubのメールに合わせる
git config user.email "your@email.com"
 
# 空コミットで強制トリガー
git commit --allow-empty -m "redeploy"
git push

Supabase(アクセスカウンター)

アクセスカウンターを本物にするためにSupabaseを使った。

create table visits (
  id bigint primary key,
  count bigint default 0,
  updated_at timestamp with time zone default now()
);
 
insert into visits (id, count) values (1, 0);
 
alter table enable row level security;
create policy "Allow public read" on visits for select using (true);
create policy "Allow public update" on visits for update using (true);

フロントから直接叩く構成なので、環境変数は.env.localに書いてVercelの環境変数にも登録する。

VITE_SUPABASE_URL=https://xxxx.supabase.co
VITE_SUPABASE_ANON_KEY=sb_publishable_xxxx

.gitignore.envが入っていることを必ず確認すること。


実装したイースターエッグ(一部)

詳細なトリガーはサイトで探してほしいが、実装の概要だけ。

深夜猫(0:00〜4:59限定)

const isNight = time.getHours() >= 0 && time.getHours() < 5;
// ...
{isNight && <NightCat />}

深夜だけタスクバー猫が現れる。GIFはGeminiで生成してEZGIFで背景透過した。

テーマ切り替え(Win98 / WinXP)

ThemeContextでグローバルにテーマを管理して、スタートボタンクリックで切り替わる。XPモードではBlissの壁紙(あの緑の丘)が出る。

デフラグスクリーンセーバー

一定時間操作がないと起動する。赤・青・黒のブロックが並び「1.22GBの断片を解消中...」と永遠に1%のまま進む。

コナミコマンドで🦀の雨

const KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown',
                 'ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a'];

↑↑↓↓←→←→BA で🦀が降り注ぐ。それだけ。

動くイルカ(クリッピー的存在)

画面右下を泳ぎ回る。吹き出しのセリフは平成ネットミームを100種類詰め込んでランダム表示。


TypeScriptでハマったこと

verbatimModuleSyntaxが有効な場合、通常のimportでReactNodeを読み込むとエラーになる。

// ❌ NG
import { useState, ReactNode } from "react";
 
// ✅ OK
import { useState } from "react";
import type { ReactNode } from "react";

1500行のスパゲッティについて

現在Home.tsxは1500行を超えている。

コンポーネント分割?リファクタリング?知らない。TypeScriptのビルドが通っている以上、動いている。これは当時の個人サイト管理人のメンタリティの再現でもある。

ソースコードを読んだ人だけへのイースターエッグもある。


「見た目は平成、中身は令和」

当時の本物の個人サイトはHTMLが崩壊していても「なんとなく表示される」だった。このサイトは1500行スパゲッティでもTypeScriptが型チェックして、ビルドが通った瞬間に完璧に動く。

誰もReactだとは思わない。DevToolsを開いてビルド済みJSのサイズを見て初めて「え?」となる。それが一番のイースターエッグかもしれない。


おわりに

$12/年で遊び放題だ。サブドメイン取って色々公開したいと思っている。


えなどり / Indie Developer
Zenn: https://zenn.dev/ena_dri

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?