こんにちは!
福岡工業大学 情報技術研究部所属
今年2年生になる無音といいます!
私の考えたアーキテクチャ最強じゃないか???
ってことで布教をしようと考えこの記事を書きました!
ぜひご活用ください!
免責事項: この記事で紹介するアーキテクチャは、実際の業務システムには採用しないことを強くお勧めします。 あくまでエイプリルフールネタの一つです。
突然ですが、質問です
「フロントエンドは何で書きますか?」
React?Vue?Svelte?
正解です。でも、本当に正解ですか?
フロントエンドはC言語で書けばいい。バックエンドはCSSで書けばいい——そう気づいてしまったのです!
その名も──CCSS(C + CSS Architecture)。
CCSSとは何か
C言語でフロントエンドを書く
「え、CはWebと関係なくない?」
そう思った方、正常です。でもC言語はsnprintf()でなんでも文字列にでき、fputs()でファイルに書ける——HTMLだって書ける。つまりフロントが書ける。論理は完璧なのです
C言語でHTMLを生成するとはつまりこういうこと!:
char buf[1024];
snprintf(buf, sizeof(buf), "<h3>%s</h3>\n", name); // name は事前にXSSエスケープ済み
fputs(buf, fp);
これは冗談ではなく、実際に動くコードです。
CSSでバックエンドを書く
「さすがにCSSはスタイルシートでは?」
正解!でもCSSには:checked疑似クラスも:has()セレクタもある——つまり**状態管理ができる!!!**それができるなら、バックエンドも書ける!当然ですよね?
実際のC-portfolioのフィルタリング:
/* Web カテゴリ以外を非表示 */
.filter-container:has(#filter-cat-0:checked) .project-card:not(.category-0) {
display: none;
}
アコーディオン展開:
.detail-toggle:checked ~ .project-detail-wrapper {
grid-template-rows: 1fr; /* 0fr → 1fr でスムーズに展開 */
margin-top: var(--space-sm);
}
ラジオボタンとチェックボックスのオン・オフでUI状態を管理しています——JavaScriptは一切ない!これがバックエンドです!(は?)
3つの実績
1. ポートフォリオ──原点にして頂点
最初に試したのが自分のポートフォリオ作成「ポートフォリオをC言語で実装する」という、採用担当者の頭に?が浮かぶ企画!
C言語でHTMLを静的生成するSSGを自作し、GitHub Actionsで自動ビルド&デプロイ
ビルドは至ってシンプル!:
CC = gcc
CFLAGS = -Wall -Wextra -Werror -pedantic -std=c11
TARGET = generator
SRCS = src/main.c src/render.c src/data.c
all: build generate
$(TARGET): $(SRCS) src/models.h src/render.h
$(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
generate: $(TARGET)
@mkdir -p dist
./$(TARGET)
@cp styles/main.css dist/main.css
makeするとgccがコンパイルし、./generatorを実行するとdist/index.htmlが生成される
フッターにはGenerated by pure Cの文字と、__DATE__と__TIME__マクロで生成日時が埋め込まれている。
セキュリティ設計が妙に本格的なのもこのプロジェクトの特徴です!
XSS対策として専用のエスケープ関数を実装し、さらにバッファオーバーフロー防止のためにchecked_snprintfというラッパー関数まで作りました!:
/* html_escape(): & < > " ' を安全な実体参照に変換 */
void html_escape(const char *src, char *dest, size_t dest_size) {
size_t di = 0;
for (size_t si = 0; src[si] != '\0' && di < dest_size - 1; si++) {
const char *rep = NULL;
switch (src[si]) {
case '&': rep = "&"; break;
case '<': rep = "<"; break;
case '>': rep = ">"; break;
case '"': rep = """; break;
case '\'': rep = "'"; break;
default: break;
}
if (rep) {
size_t len = strlen(rep);
if (di + len >= dest_size) break;
memcpy(dest + di, rep, len);
di += len;
} else {
dest[di++] = src[si];
}
}
dest[di] = '\0';
}
/* checked_snprintf(): vsnprintfの戻り値を必ず検証 */
static int checked_snprintf(char *buf, size_t buf_size,
const char *context, const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int ret = vsnprintf(buf, buf_size, fmt, args);
va_end(args);
if (ret < 0 || (size_t)ret >= buf_size) {
fprintf(stderr, "エラー: %s バッファ超過\n", context);
return 0;
}
return 1;
}
Webフロントエンドなのにメモリ安全性を本気で考えた
——malloc/free は一切使わず、スタック領域だけで完結させてしまった...「フロントとバックを逆にしたら、フロントエンジニアがバッファ管理を考える羽目になる」という、当然の結末です!
すばらしい!
動的UIはCSSの:checkedと:has()で実現しました
チェックボックスで詳細を展開し(grid-template-rows: 0fr → 1fr のアニメーション)、ラジオボタン + :has() でカテゴリフィルタリング
——すべてJavaScriptゼロ!!!
ただし問題が浮上しました...手書きは案外辛い
そこで活用したのがAIです!
方法は「仕様書を先に自分で書いて、AIに読ませてコードを書いてもらう」
——docs/ccss/ に6本の設計ドキュメントを書き上げてそれを渡しました!
でも最初に「CCSSを実装して」と頼んだら「そんなものは存在しない!」と怒られてしまった...(自分で考えたアーキテクチャだから当然ですね!!!)
ドキュメントを書く → AIに読ませる の順番さえ守れば、AIは存在しないアーキテクチャでも実装してくれます!!!
AI優秀!!!
ホントにC言語でHTMLをタグ単位で文字列として書くのは人類には向いていないと感じました...
——「フロントとバックを逆にしたら開発体験も逆になる」当然の話ですけどね
2. ハッカソン「メガロカップ 2026」──コンパイラを自作する
ポートフォリオの反省から生まれた思想:「CCSSは自動生成するべき!」
——そもそもAIに書かせるとうまくいかない
そして作った。CCSSトランスパイラ(独立CLIパッケージ @ccss/compiler)。
TypeScript(TSX)を入力としてC言語とCSSのソースを出力する、ソース→ソース変換のトランスパイラ!
——AIを使って8時間ぐらいで完成させた!
parser → ir → emitter の3ステージ構成で、3種のファイルを自動生成する:
parser.ts → TypeScript AST解析(ReactのuseState・JSXを解析)
ir.ts → 中間表現に正規化(状態IDを ccss:page:component:state-name 形式で自動採番)
emitter/ → C言語(ui.generated.c)・CSS(ui.generated.css)・JSON(ccss.manifest.json)を生成
たとえばこんなReactコンポーネントを入力すると:
export function AppShell() {
const [authModalOpen, setAuthModalOpen] = useState(false)
const [newPostModalOpen, setNewPostModalOpen] = useState(false)
return (
<div className="relative z-0 flex justify-center font-sans">
<input id="ccss:app-shell:app-shell:auth-modal-open" type="checkbox" />
<input id="ccss:app-shell:app-shell:new-post-modal-open" type="checkbox" />
{/* ... */}
</div>
)
}
トランスパイラがC言語を吐き出す:
/*
* Auto-generated by @ccss/compiler
* Component: AppShell
* GeneratedAt: 2026-03-20T04:48:57.769Z
* State IDs:
* - ccss:app-shell:app-shell:auth-modal-open
* - ccss:app-shell:app-shell:new-post-modal-open
*/
const char* ccss_render_app_shell(void) {
return "<div class=\"relative z-0 flex justify-center font-sans\">"
"<input id=\"ccss:app-shell:app-shell:auth-modal-open\" "
"class=\"ccss-state-input\" type=\"checkbox\"></input>...";
}
同時にCSSも吐き出す:
.ccss-state-input {
position: absolute;
opacity: 0;
pointer-events: none;
}
[data-ccss-state="ccss:app-shell:app-shell:auth-modal-open"] {
display: none;
}
#ccss\:app-shell\:app-shell\:auth-modal-open:checked
~ [data-ccss-state="ccss:app-shell:app-shell:auth-modal-open"] {
display: block;
}
チェックボックスはopacity: 0で完全に隠れているが、CSSの:checkedセレクタはしっかり検知します!
見えない入力フォームでUIを動かす——これがCCSSのバックエンドの正体なのです。
このトランスパイラをスワイプで風を起こしてキャラクターを動かす物理演算ゲーム(KAPLAY製)に組み込んだ
「既存ゲームにCCSSを後付けする」という課題には、DOMを完全分離する設計で対応しました:
-
#ccss-ui-root— C言語がHTMLを差し替える専用領域 -
#ccss-game-root— KAPLAYのCanvas専用領域(絶対に混ぜない)
C言語がHTMLを全体差し替えしても、Canvasの内部状態が初期化されないゲームとCCSSが互いの領域を侵さない設計です
さらに安全装置も整備しました!
コード生成の品質を保証するために、検証スクリプトが7本もあるのです:
pnpm ccss:manifest-check # manifestスキーマ検証(状態ID形式、型整合)
pnpm ccss:selector-check # :checked セレクタ対応確認
pnpm ccss:html-state-check # 生成HTMLに全stateIDが存在するか確認
pnpm ccss:c-safety # 危険なAPIを検出(malloc, strcpy, system...)
pnpm ccss:css-safety # 危険なトークンを検出(@import, url()...)
pnpm ccss:dom-isolation # <canvas>の混入チェック
pnpm ccss:transpile-build # 上記を一括実行
C言語にmallocが混入していないかチェックするCI
——言葉の響きがすでに楽しい!(おかしい)
これは作ってよかった安全装置でした!
CはWebとは無関係に育ってきた言語で、野放しにするとすーぐ危険なことをやろうとする...
結果:7位(圏外発表だったが主催者に確認)
個人的には満足のいく結果だった!
3. Web Speed Hackathon 2026──CCSSは本番の洗礼を受ける
CyberAgent主催のパフォーマンス改善競技で、既存のSNSアプリ「CaX」のLighthouseスコアを最大化するものでした
ここにもCCSSを入れてみた
そして盛大に失敗した。
失敗その1:nav要素の幽霊問題
FCP(First Contentful Paint)を改善するために、index.htmlに静的なフォールバックnavを事前レンダリングする戦略を行ってみた
ReactのPortalが動的にnavを挿入する前に、ユーザーにナビゲーションを見せるという発想
<!-- index.html: Reactが来る前に見せるフォールバックnav -->
<aside id="ccss-nav-root" class="relative z-10">
<nav class="border-cax-border bg-cax-surface fixed ...">
<!-- 32行のHTMLナビゲーション -->
</nav>
</aside>
Reactがここに<nav>を挿入すると<aside>の中に<nav>が2つ生まれる。対策として3段階の試行錯誤をしました:
第1段階:useEffectで削除(失敗)
useEffect(() => {
const staticNav = navRoot?.querySelector("nav");
if (staticNav) staticNav.remove();
}, [navRoot]);
第2段階:CSS :has() で制御(失敗)
/* navが2つ存在するときだけ最初のnavを隠す */
#ccss-nav-root:has(nav + nav) > nav:first-child {
display: none;
}
第3段階:静的navを完全削除(解決)
<!-- 最終形: フォールバックnavを全て削除 -->
<aside id="ccss-nav-root" class="relative z-10">
<!-- React Portal renders Navigation component here -->
</aside>
結局、「静的navを持つ」という戦略そのものを諦めた...
——PlaywrightのtoBeVisible()が非表示の先頭nav要素を「見えるnav」と誤判定し、テストがタイムアウトし続けたのが引き金でした...
「存在するけど見えないnav」を「見えるnav」と判定する...とても哲学的である
失敗その2:Animated WebPをvideoタグで再生しようとした
// 失敗版: <video>でAnimated WebPを再生しようとした
<video
autoPlay loop muted playsInline
src={src} // ← .webpファイル
onLoadedMetadata={() => setIsLoaded(true)} // 永遠に呼ばれない
/>
HTML5のvideoはAnimated WebPを再生できない!
——readyStateが0のまま固まり、E2EのwaitForVisibleMedia()が永遠に待ち続けてしまった
解決策はcanvasでのフレームベース描画への切り替えでした...:
// 修正版: canvasで手動描画
const img = new Image();
const draw = () => {
if (!state.playing) return;
const ctx = canvas.getContext("2d");
if (ctx) ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
state.animId = requestAnimationFrame(draw);
};
img.onload = () => { state.animId = requestAnimationFrame(draw); };
img.src = src;
最終的な結末
E2Eテストを通すために、CCSSによる状態管理を順番に取り除きました...
モーダルやメニューのCSSチェックボックス制御が、普通のReact useState + HTML Dialog に置き換えられていった、完全敗北です。:
Before (CCSS):
- <input type="checkbox" id="ccss-auth-modal" class="ccss-state-input" />
- body:has(#ccss-auth-modal:checked) .auth-modal-overlay { display: flex; }
After (普通のReact):
+ const [isAuthModalOpen, setIsAuthModalOpen] = useState(false)
+ {isAuthModalOpen && <Modal>...</Modal>}
CCSSトランスパイラが生成したファイル(ui.generated.c、ccss.manifest.json、ui.generated.css)はリポジトリに残り続けました
コードは正しかったし、トランスパイラも動いた!
ただ、E2Eテスト全52件を通すためには、CCSSは邪魔だった( ;∀;)
CCSSから得た視点
正直に言えば、CCSSは実用的なアーキテクチャではないとは感じました。
でも意味のない実験は一つもなかったと私は考えます。
| 学んだこと | どこで |
|---|---|
CのバッファとWebセキュリティは地続きである!html_escapeとchecked_snprintfを真剣に書いたことで、Webセキュリティへの解像度が上がった |
C-portfolio |
| 「手書きが辛い」はトランスパイラで解決できる。TypeScript ASTを解析してC言語を出力する3ステージ(parser → ir → emitter)を書いた経験はどこでも使える | megalo2026 |
| E2Eテストは「動いてる感」の錯覚を打ち砕く!テストが通るまでCCSSが「動いている」は錯覚に過ぎなかった | web-speed-hackathon |
そして何より:テストを先に書くべきだった。
E2E・VRTテストは、CCSSが実際のブラウザ・DOM・レンダリングパイプラインに乗ったときに何が起きるかを正直に教えてくれました。
多少失敗だったが、とても勉強になりました!!!
変なことをやると、変なところで知識がつながる
おわりに
CCSSは最先端のアーキテクチャだ。
…というのは冗談として、CSSで状態管理はマジでできます!
:checkedと:has()でモーダルの開閉を管理することは技術的に正しく動くし、C言語でHTMLを生成することも動く、トランスパイラを書けば開発体験の問題も解決できる!
問題は「動く」と「テストが通る・保守できる」は別物だということ
CCSSを通じて、「なぜJavaScriptとReactが今の地位にあるのか」を逆から証明した気がします
CSSはバックエンドで使えます!ぜひ試してみてください。
やってみると、Webの「当たり前」がなぜそこに存在するのかの輪郭がくっきり見えてくる
——「その体験だけでも、価値がある」と自分は思いました!
C言語でフロントを書くと、さらにその感覚が強烈になります!!!
——C言語超おすすめしてます!!!!!
CCSS: C + CSS Architecture
____/ 動作確認済み環境: ハッカソンのステージ
______/ 推奨環境: 深夜の実験・ポートフォリオの差別化