7ch 技術ドキュメント
#プロジェクト概要
7ch 匿名交流のためのテキストコミュニティで、低い参入障壁と軽量なアイデンティティを重視しています。
本番環境はフロントエンドとバックエンドを分離した構成です。ブラウザ SPA が UI と操作を担い、API サービスが機能を提供し、Postgres が内容を保存します。フロントは Vercel、バックエンドは Render、DB は Neon に配置されています。
#アーキテクチャとデプロイ
システムはブラウザクライアント、API サービス、データベースの三層で構成されます。クライアントは /api を通じてのみ通信し、サーバー側でレート制限・フィルタリング・ID 計算を行った上で保存されます。
ルーティングとページ構成
主なページパス(簡略):
/ -> Home (Boards) /board/:boardId -> Board /board/:boardId/thread/:threadId -> Thread /favorites -> Favorites /docs | /help | /terms | /privacy | /QA -> Static Pages
板・スレッド・投稿などのデータはすべて API から取得され、クライアントは言語設定やフォロー/非表示などの好みだけを保存します。
#データモデルと契約
クライアントが扱う主要エンティティは Board / Thread / Post です。サーバー側には IP やタイムスタンプ等の内部フィールドがありますが、公開 API では必要なフィールドのみ返します。
エンティティ
- Board: 板は固定定義で、ID・名称・説明を持ちます。
- Thread: スレッドはタイトル、返信数、閲覧数、更新日時、OP プレビューを含みます。
- Post: 投稿は表示名、Tripcode、本文、Daily ID などを持つ各階層レコードです。
{
"id": 12,
"threadId": "<uuid>",
"name": "Anonymous",
"tripcode": "◆abcd1234ef",
"content": "...",
"createdAt": "2026-02-03T12:00:00Z",
"uid": "A1b2C3d4",
"isOp": false
}#匿名性と主要メカニズム
1. Daily ID(毎日変わる匿名 ID)
Daily ID は同一板・同一日における発言者の識別に使われ、長期的な追跡を避けるため毎日変化します。
- 入力:クライアントの送信元 IP、現在日付(UTC)、板 ID、サーバーの秘密ソルト。
- 処理:SHA-256 ハッシュ → Base64 URL Safe(パディングなし)→ 先頭 8 文字を使用。
- 出力:ID:A1b2C3d4 のような短い識別子。
同一 IP は同一板・同一日で安定。日付変更、板の変更、ネットワーク変更で ID は変化します。共有回線(NAT 等)では同じ ID になる場合があります。
Raw = IP + Date(UTC: YYYY-MM-DD) + BoardId + SecretSalt Hash = SHA256(Raw) Encoded = Base64UrlSafeNoPad(Hash) DailyId = Encoded.substring(0, 8)
2. Tripcode(トリップ)
名前欄に Name#password を入力すると Tripcode が生成され、同一人物であることを示せます。パスワードは保存せず、短いハッシュ片のみを保持します。
Input: "Name#password" Name = "Name" Tripcode = "◆" + hex(SHA256(password + SecretSalt)).slice(0, 10)
3. Sage(下げ)
Email 欄に sage を含めると(大文字小文字を区別しません)、返信してもスレッドを上げません。
const isSage = email?.toLowerCase().includes('sage');
if (!isSage) {
thread.updatedAt = now; // bump only when NOT sage
}4. 引用とプレビュー
クライアントが >>123 の引用を解析し、ホバー時にプレビューを表示します。サーバーは本文をそのまま保存します。
5. フィルタリングと風紀対策
サーバー側で NG ワードの置換と基本的なレート制限を行い、スパムや攻撃を抑制します。
#国際化とローカライズ
現在は中国語(zh-CN)と日本語(ja-JP)に対応しています。
react-i18next を使用し、日本語の日時表記を最適化しています:
- 和暦日付:例として 2025 は R7 と表示。
- 曜日: (月)、(火) など。
if (d.getFullYear() >= 2019) y = 'R' + (d.getFullYear() - 2018); // Reiwa else if (d.getFullYear() >= 1989) y = 'H' + (d.getFullYear() - 1988); // Heisei