Resources
8Install
npx skillscat add masakihirokawa/rorklab-net Install via the SkillsCat registry.
We need to produce a 2-3 sentence plain-text summary in English, objective, factual, no marketing language, no superlatives, no calls to action. Must be at most 60 words. No quotes, no markdown formatting, no bullet points, no headings. Just plain text. We need to explain what the skill does, what problem it solves, when to use it. The skill is a site construction template for rorklab.net, summarizing architecture, content pipeline, etc. It provides a template for building knowledge base sites with Next.js, MDX, i18n, etc.
Rork Lab サイト構築テンプレート — SKILL.md
このスキルファイルは rorklab.net の構築手順・アーキテクチャ・運用方法をまとめたものです。
同じ雛形で新しいナレッジベースサイトを立ち上げる際のリファレンスとして使えます。
アーキテクチャ概要
| レイヤー | 技術 |
|---|---|
| フレームワーク | Next.js 16 (App Router, Turbopack) |
| 言語 | TypeScript |
| スタイル | Tailwind CSS 4 + CSS Variables (ダーク/ライト対応) |
| i18n | next-intl v4(ja / en) |
| コンテンツ | MDX → ビルド時にJSONへプリコンパイル |
| ホスティング | Cloudflare Pages(OpenNext アダプター) |
| 決済 | Stripe Checkout Sessions(サブスクリプション) |
| 分析 | Google Analytics (gtag.js) |
| OGP | 静的 PNG(public/og/rorklab-og.png、1200×1200px)— 全ページ共通 |
| 検索 | クライアントサイド検索(タグ・タイトル・説明文のスコアベースランキング) |
| RSS | /feed.xml (ja), /en/feed.xml (en) — 最新50件 |
| サイトマップ | 動的生成(hreflang付き、記事・ブログ・静的ページ) |
サイト規模(2026-03-21 現在)
- 記事: JA 153本 / EN 130本 = 283エントリ(うちプレミアム JA 38本 / EN 27本)
- ブログ: JA 9本 / EN 9本 = 18エントリ
- カテゴリ: rork-dev (88), rork-basics (26), rork-business (19), rork-ai (16), app-dev (4)
ディレクトリ構成
├── content/
│ ├── articles/
│ │ ├── ja/{category}/{slug}.mdx # 日本語記事
│ │ └── en/{category}/{slug}.mdx # 英語記事
│ └── blog/
│ ├── ja/{slug}.mdx
│ └── en/{slug}.mdx
├── public/
│ ├── icon.svg, icon-192.png, icon-512.png, apple-touch-icon.png
│ ├── favicon-16.png, favicon-32.png, favicon-48.png
│ ├── robots.txt # Sitemap + RSS 参照、AI クローラー許可
│ └── llms.txt # LLM アクセス情報
├── scripts/
│ └── generate-content.mjs # MDX → JSON プリコンパイル
├── src/
│ ├── app/
│ │ ├── layout.tsx # ルートレイアウト(メタデータ基盤)
│ │ ├── globals.css # テーマ変数 + 記事スタイル
│ │ ├── sitemap.ts # 動的サイトマップ(hreflang付き)
│ │ ├── not-found.tsx
│ │ ├── feed.xml/route.ts # RSS フィード(日本語)
│ │ ├── en/feed.xml/route.ts # RSS フィード(英語)
│ │ ├── [locale]/
│ │ │ ├── layout.tsx # ロケール別レイアウト(GA, フォント, テーマ, RSS link)
│ │ │ ├── page.tsx # ホームページ(JSON-LD WebSite schema)
│ │ │ ├── HomeClient.tsx # ホームページUI(クライアント)— 「すべての記事 (N) →」で総記事数を表示
│ │ │ ├── opengraph-image.tsx # OGP fallback(実質未使用)
│ │ │ ├── articles/
│ │ │ │ ├── page.tsx # 記事一覧(ページネーション + カテゴリフィルター)
│ │ │ │ └── [category]/
│ │ │ │ ├── page.tsx # カテゴリ別一覧
│ │ │ │ └── [slug]/
│ │ │ │ └── page.tsx # 記事詳細(TOC, シェア, 関連記事)
│ │ │ ├── blog/
│ │ │ │ ├── page.tsx
│ │ │ │ └── [slug]/
│ │ │ │ └── page.tsx
│ │ │ ├── guides/page.tsx
│ │ │ ├── support/page.tsx # サポートページ(Ko-fi, PayPal, Wise, Revolut)
│ │ │ ├── privacy/page.tsx
│ │ │ ├── membership/page.tsx # メンバーシップページ(プラン一覧 + プレミアム記事一覧)
│ │ │ ├── terms/page.tsx
│ │ │ └── tokusho/page.tsx
│ │ └── api/
│ │ ├── checkout/route.ts # Stripe Checkout Session 作成(Pro: subscription / Premium: payment)
│ │ ├── customer-portal/route.ts # Stripe Customer Portal
│ │ ├── verify-session/route.ts # Stripe セッション検証 → KV 保存 → Cookie 発行
│ │ ├── webhook/route.ts # Stripe Webhook 受信 → KV 書き込み
│ │ ├── restore-access/route.ts # Cookie 消失時のアクセス復元
│ │ └── search-data/route.ts # 検索用 JSON API(タグ・レベル含む)
│ ├── components/
│ │ ├── layout/
│ │ │ ├── Header.tsx # 固定ヘッダー(検索, テーマ, 言語, ♥サポート, メンバーシップリンク)
│ │ │ ├── Footer.tsx
│ │ │ ├── LocaleSwitcher.tsx # 言語切り替え
│ │ │ ├── ThemeProvider.tsx # ダーク/ライトモード
│ │ │ └── ThemeToggle.tsx
│ │ ├── mdx/
│ │ │ ├── Callout.tsx # info/warning/tip/danger
│ │ │ └── mdx-components.tsx
│ │ └── ui/
│ │ ├── ArticlePagination.tsx # ページネーション(← 1 2 3 ... →)
│ │ ├── BookRecommendation.tsx # Amazon アソシエイト書籍
│ │ ├── GrainOverlay.tsx # フィルムグレインエフェクト
│ │ ├── LevelBadge.tsx # 初級/中級/上級バッジ
│ │ ├── MembershipCTA.tsx # 無料記事末尾に自動表示するメンバーシップ誘導CTA
│ │ ├── NewsTicker.tsx # 画面上部のスクロールニュース
│ │ ├── PremiumPaywall.tsx # 有料記事のペイウォール(Pro/Premiumボタン)
│ │ ├── RelatedArticles.tsx # 関連記事(スコアベース: カテゴリ+3, タグ+2)
│ │ ├── ScrollToTop.tsx # フローティング「トップへ戻る」ボタン
│ │ ├── SearchModal.tsx # 検索モーダル(タグ検索, キーボード操作, ハイライト)
│ │ ├── ShareButtons.tsx # X / はてブ / LINE シェア
│ │ └── TableOfContents.tsx # 目次自動生成(h2/h3, IntersectionObserver)
│ ├── generated/
│ │ ├── articles.json # プリコンパイル済み記事データ
│ │ └── blog.json # プリコンパイル済みブログデータ
│ ├── i18n/
│ │ ├── config.ts, routing.ts, request.ts, navigation.ts
│ │ └── messages/{ja,en}.json # UIテキスト翻訳
│ ├── lib/
│ │ └── content.ts # 記事・ブログ取得API
│ └── middleware.ts # next-intl ミドルウェア(※ OpenNext が proxy 未対応のため維持)
├── wrangler.toml # Cloudflare Workers 設定
├── open-next.config.ts # OpenNext アダプター設定
├── SKILL.md # ← このファイル
├── CONTENT_LOG.md # 全記事・ブログの管理ログ
├── REMAINING_TASKS.md # 残タスクリスト
└── package.json新規サイト立ち上げ手順
1. プロジェクト初期化
npx create-next-app@latest {site-name} --typescript --tailwind --app --src-dir
cd {site-name}
npm install next-intl@^4 gray-matter unified remark-parse remark-rehype rehype-raw rehype-stringify stripe
npm install -D rehype-pretty-code shiki @opennextjs/cloudflare wrangler2. i18n セットアップ(next-intl v4)
src/i18n/routing.ts— ロケール定義(['ja', 'en'], defaultLocale:'ja')src/i18n/request.ts— リクエストごとのロケール解決src/middleware.ts— next-intl のミドルウェア設定src/i18n/messages/{ja,en}.json— UI翻訳テキストsrc/app/[locale]/layout.tsx— ロケールに応じた<html lang>
3. コンテンツシステム
MDX 記事のフロントマター:
---
title: "記事タイトル"
slug: "url-slug"
category: "category-name"
level: "beginner|beginner-intermediate|intermediate|intermediate-advanced|advanced"
date: "YYYY-MM-DD"
updated: "YYYY-MM-DDTHH:MM" # ソート優先度制御(任意)
author: "サイト名"
description: "メタディスクリプション"
tags: ["tag1", "tag2"]
premium: false # 有料記事の場合 true
---ソートルール: updated フィールドがあればそれを優先、なければ date。同日の記事は updated のタイムスタンプで順序を制御する。
プリコンパイル: scripts/generate-content.mjs で MDX を HTML に変換し src/generated/articles.json に出力。npm run generate で手動実行(注意: package.json の prebuild は generate にリネームすること。prebuild のままだと npm run build のたびに自動実行されて Cloudflare Pages で OOM が発生する)。
カテゴリ定義: src/lib/content.ts の CATEGORIES 配列でアイコン・カラーを設定。
4. テーマシステム(ダーク/ライト)
- CSS Variables で全色をテーマ対応(
globals.css) ThemeProvider+ThemeToggleで localStorage ベースの切り替え- FOUC 防止のブロッキングスクリプトを
<head>に挿入
5. OGP 画像
public/og/rorklab-og.png(1200×1200px 正方形)が全ページの OGP 画像として設定されている(src/app/layout.tsx の openGraph.images に直接 URL を指定)。LINE・Stripe Checkout のサムネイルにも正方形が最適。画像を更新する場合は _material/OGP images/ のデザインを編集してから public/og/rorklab-og.png に上書きコピーし、push する。
※ 記事・ブログ個別の OGP 画像は削除済み。全ページが共通画像を継承する。
6. SEO 設定
src/app/layout.tsx— ベースメタデータ(keywords, robots, authors)src/app/[locale]/page.tsx— ロケール別メタデータ + JSON-LD (WebSite + FAQPage)- 記事ページ — JSON-LD (Article + BreadcrumbList) + hreflang alternates
src/app/sitemap.ts— 動的サイトマップ(hreflang, 記事実日付, ブログ含む)public/robots.txt— Allow: / + Sitemap + RSS 参照 + AI クローラー許可- RSS フィード —
/feed.xml(ja),/en/feed.xml(en) <head>に RSS alternate link
7. Cloudflare Pages デプロイ
# ビルド
npx opennextjs-cloudflare build
# デプロイ(GitHub連携推奨)
git push origin main # Cloudflare Pages が自動デプロイ
# 手動デプロイ(環境変数 CLOUDFLARE_API_TOKEN が必要)
CLOUDFLARE_API_TOKEN=xxx npx wrangler pages deploy .open-next/assets --project-name={project-name}Cloudflare Pages 設定:
- Build command:
npx opennextjs-cloudflare build - Build output directory:
.open-next/assets - 環境変数: STRIPE_SECRET_KEY, STRIPE_PRICE_ID(必要に応じて)
⚠️ 重要 — generate-content.mjs の OOM 対策:
unified()プロセッサをモジュール先頭で1回だけ作成し.freeze()を呼ぶこと。記事ごとにunified()を新規作成すると shiki WASM が毎回初期化されて Cloudflare Pages ビルドが OOM で失敗する。
8. Stripe 統合
src/app/api/checkout/route.ts— Checkout Session 作成(subscription mode)src/app/api/customer-portal/route.ts— Customer Portal リダイレクトsrc/app/api/verify-session/route.ts— Stripe セッション検証 → Cloudflare KV 保存 → Cookie 発行src/app/api/webhook/route.ts— Stripe Webhook 受信 → KV 書き込みsrc/app/api/restore-access/route.ts— Cookie 消失時にメールアドレスで再認証PremiumPaywall.tsx— 有料記事のペイウォール UI(Pro/Premium 両ボタン)MembershipCTA.tsx— 無料記事末尾に自動表示(非プレミアム会員にのみ表示)src/app/[locale]/membership/page.tsx— メンバーシップページ(プラン比較 + premium記事一覧)- Cloudflare KV
PREMIUM_ACCESS— メール→Cookie トークンの保存(wrangler.toml にバインディング設定済み) public/stripe.png— Stripe 商品サムネイル(1200×1200px 正方形)
9. Google Analytics
src/app/[locale]/layout.tsx の <head> に gtag.js を配置:
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX" />
<script dangerouslySetInnerHTML={{ __html: `window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','G-XXXXXXXXXX');` }} />日常運用
記事の追加
content/articles/{locale}/{category}/{slug}.mdxを作成(ja + en)node scripts/generate-content.mjsで JSON 再生成npx opennextjs-cloudflare buildでビルドgit push origin mainでデプロイ
ニュースティッカーの更新
src/components/ui/NewsTicker.tsx の NEWS_ITEMS オブジェクトを編集。
自動更新(スケジュールタスク)
claudelab-daily-content タスクが毎日 9:00 / 12:00 / 15:00 / 21:00 に実行:
_reference_keywordsの優先キーワードから記事テーマを選択_reference_urlsの参考URLから最新情報を取得- 2-4 本の記事を日英で自動生成(2000-4000語、FAQ付き)
updatedフィールドを付与してソート順を制御- ニュースティッカーを最新情報に更新
- ビルド → Git push → デプロイ確認
新サイト作成時のカスタマイズポイント
新しいナレッジベース(例: antigravitylab.net)を作る場合:
- CATEGORIES を変更 — サイトのテーマに合わせたカテゴリ定義(
src/lib/content.ts) - カラーパレット —
globals.cssの CSS Variables を差し替え - フォント — layout.tsx の Google Fonts URL を変更
- OGP デザイン —
_material/OGP images/のデザイン画像をpublic/og/rorklab-og.pngとして配置(1200×1200px) - favicon — SVG → Pillow 等で全サイズを生成(16, 32, 48, 192, 512, apple-touch-icon)
- i18n メッセージ —
src/i18n/messages/のUI翻訳テキスト - BookRecommendation — Amazon Associates のストアID変更
- Stripe — 新しい Product / Price を作成
- Google Analytics — 新しいプロパティの測定ID
- RSS フィード —
feed.xml/route.tsのサイト名・説明文を変更 - robots.txt — Sitemap URL を新ドメインに
- wrangler.toml — プロジェクト名を変更
- スケジュールタスク — 新サイト用のキーワードリスト・参考URLに差し替え
コア構造(App Router, MDX システム, コンテンツAPI, 検索, ページネーション, SEO 基盤, RSS, サイトマップ, Cloudflare デプロイ)はそのまま再利用可能です。
技術スタック詳細
| パッケージ | バージョン | 用途 |
|---|---|---|
| next | 16.1.6 | フレームワーク |
| react / react-dom | 19.2.3 | UI ライブラリ |
| next-intl | ^4.8.3 | i18n |
| gray-matter | ^4.0.3 | MDX フロントマター解析 |
| unified + remark + rehype | ^11 | MDX → HTML 変換 |
| rehype-pretty-code + shiki | コードハイライト | |
| stripe | ^20.4.1 | 決済 |
| @opennextjs/cloudflare | ^1.17.1 | Cloudflare Pages アダプター |
| tailwindcss | ^4 | スタイル |
| wrangler | ^4.71.0 | Cloudflare CLI |
⚠ 既知の問題と対応(2026-03-13)
1. MDX 内ハードコードの関連記事リンクによる架空記事表示
症状: 記事ページに「関連記事」が2重に表示される。上側の関連記事がリンク切れ(404)になる。
原因: 一部の MDX ファイルに ## 関連記事 / ## Related Articles セクションがハードコードされており、
動的な RelatedArticles.tsx コンポーネントとは別に表示されていた。
さらにそのリンクのURLが /articles/ja/category/slug(正しくは /articles/category/slug)や/articles/en/category/slug(正しくは /en/articles/category/slug)という
間違ったロケールプレフィックス形式だった。
正しいURL形式(next-intl localePrefix: "as-needed" の場合):
- 日本語(デフォルトロケール):
/articles/{category}/{slug} - 英語:
/en/articles/{category}/{slug}
対応:
- MDX ファイル内のハードコード
## 関連記事セクションを全削除(RelatedArticles.tsxが動的に処理するため不要) - MDX 内に残るインラインリンクのロケールプレフィックスを修正
scripts/generate-content.mjsにバリデーションを追加:
コンパイル後 HTML にhref="/articles/ja/またはhref="/articles/en/が含まれる場合、
コンソールに⚠ WARNINGを出力するようにした(誤ったリンクの早期検出のため)。
2. Tailwind v4 Preflight によるリストスタイル消去
症状: 記事本文の <ul> / <ol> リストに「・」や番号が表示されない。
原因: @import "tailwindcss" (Tailwind v4)の Preflight CSS が list-style: none を全 ul/ol に適用する。
対応: globals.css の .article-content ul/ol に list-style-type と li::marker スタイルを明示指定:
.article-content ul { list-style-type: disc; }
.article-content ul ul { list-style-type: circle; }
.article-content ol { list-style-type: decimal; }
.article-content li::marker { color: var(--text-secondary); }3. パンくずリストのロケール未対応
症状: 日本語ページの記事パンくずリストに「Articles」と英語表示になる。
対応: src/app/[locale]/articles/[category]/[slug]/page.tsx のパンくずリストを修正:
{locale === "ja" ? "記事一覧" : "Articles"}