event-manual / chapter 3

5分で新イベントを立ち上げる

apps/starter

apps/starter が参照実装である。新イベントは starter をコピーして以下の6ファイルを埋めるだけで、admin(共有パスワード認証)・schema 駆動 CMS・セッションカードスタジオが動く。

1. workspace を追加する

apps/<event>/package.json を作る。@event/ は workspace 解決("")で参照する。

{
  "name": "my-event",
  "scripts": { "dev": "next dev -p 3401", "build": "next build", "typecheck": "tsc --noEmit" },
  "dependencies": { "@event/fours-sdk": "*", "@event/schema": "*", "next": "^15.2.3", "react": "^19.2.0", "react-dom": "^19.2.0" }
}

2. next.config — createNextConfig + transpilePackages

// next.config.ts
import { createNextConfig } from "@event/core";

// distDir 分離 / dev memory cache / removeConsole / セキュリティヘッダ(CSP 込み) が既定で付く
const nextConfig = createNextConfig({
  transpilePackages: [
    "@event/schema", "@event/fours-sdk", "@event/core", "@event/cms",
    "@event/session-card", "@event/shell", "@event/asset-kit",
  ],
  security: { csp: { fourS: true, vercelBlob: true } },
});

export default nextConfig;

3. adminAuth と境界 middleware

cookie 名はイベントごとに一意にする。middleware は「境界」のみで、認可の再検査は各 server action / route が行う。

// src/lib/adminAuth.ts
import { createSharedPasswordAuth } from "@event/core";
export const adminAuth = createSharedPasswordAuth({ cookieName: "starter_admin_session" });

// src/middleware.ts
import { createAuthGateMiddleware } from "@event/shell";
export const middleware = createAuthGateMiddleware({
  cookieName: "starter_admin_session",
  bypassPrefixes: ["/api/auth/"],
});
export const config = { matcher: ["/admin/:path*", "/api/admin/:path*"] };

ログイン route は adminAuth.verifyPassword() + 失敗時 loginJitter()、dev 用ワンクリックログインは createDevLoginHandler({ cookieName })/login/dev に mount する(第37章)。

4. CMS registry — schema を足すだけで admin が生える

// src/lib/cms.ts(抜粋)
import { createContentStore } from "@event/core";
import { createCmsActions, createContentReader, createResourceRegistry } from "@event/cms";

const NEWS_SCHEMA: ResourceSchema = {
  name: "news", label: "News (お知らせ)", shape: "list", primaryLabel: "title",
  csvImport: { help: "列順: title<TAB>slug<TAB>body", headers: ["title", "slug", "body"] },
  fields: [
    { type: "datetime", name: "publishedAt", label: "公開日時" },
    { type: "text", name: "title", label: "タイトル", required: true },
    { type: "image", name: "coverImageUrl", label: "カバー画像", aspect: "wide" },
    { type: "markdown", name: "body", label: "本文", rows: 10 },
    { type: "boolean", name: "visible", label: "公開する" },
  ],
};

export const registry = createResourceRegistry([NEWS_SCHEMA, SETTINGS_SCHEMA]);
export const reader = createContentReader();
export const store = createContentStore({ commitPrefix: "chore(starter-cms)" });
export const cms = createCmsActions({ registry, store, reader, requireAuth: requireAdmin });

/admin/cms/[resource] は registry から schema を引くだけの1ページで全リソースを賄う(第13章)。

5. nav registry

// src/lib/nav.ts — 中央配列のハードコード禁止。新機能はここに1行足すだけ
import { createNavRegistry } from "@event/shell";
export const nav = createNavRegistry([
  { id: "dash", group: "Overview", label: "ダッシュボード", href: "/admin", code: "DASH", order: 1 },
  { id: "cms-news", group: "コンテンツ", label: "News", href: "/admin/cms/news", code: "NEWS", order: 1 },
  { id: "card-studio", group: "ビジュアル", label: "セッションカード", href: "/admin/card", code: "CARD", order: 1 },
]);

admin layout で <AdminNav groups={nav.groups(() => true)} /><Suspense> に包んで描画する。

6. env を設定して起動

# .env.local — 最小構成
ADMIN_PASSWORD=...          # admin 共有パスワード
FOURS_EVENT_SLUG=ivs26      # 4S イベント slug
FOURS_PUBLIC_READ=1         # 公開読み取りモード
# 本番のみ: GITHUB_TOKEN / GITHUB_REPO_OWNER / GITHUB_REPO_NAME(contentStore commit)
# 画像アップロードを使うなら: BLOB_READ_WRITE_TOKEN
yarn dev:starter   # → http://localhost:3401

dev では contentStore が content/*.json へ fs 書き込みし、本番では同じコードが Octokit commit → Vercel rebuild に切り替わる(第14章)。全 env の一覧は第4章を参照。