本文へ移動
cccskills
無料GitHub で公開

route-metadata-favicons

Next.js route metadata, per-route favicons, OpenGraph, and Twitter cards. Use when adding a route or layout, adding a favicon or social share image, auditing missing metadata, naming tab titles, or touching createRouteMetadata, createDynamicRouteMetadata, generateFaviconMetadata, or titlePrefix.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md22.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Route Metadata & Favicons

Architecture Overview

FileRole
constants/favicon-route-data.tsMaster registry — favicon: { color, letter } per route
utils/favicon-utils.tsgenerateFaviconMetadata — inline SVG favicon as data:image/svg+xml URI; holds system-route overrides
utils/route-metadata.tscreateRouteMetadata / createDynamicRouteMetadata helpers — also emit OpenGraph + Twitter card metadata (override via additionalMetadata.openGraph / .twitter; per-entity ogImage)
config/extras/site.tssiteConfig.ogImage, siteConfig.description — global social defaults
app/(a)/layout.tsxRoot template: "%s — AI Matrx" — sets the brand suffix automatically

Critical: Tab Title Rule

Specific word FIRST. Category LAST. Brand handled by root template.

✅  "Build | Agents — AI Matrx"    ← first word differs — easy to scan 20 tabs
✅  "Run | Agents — AI Matrx"
✅  "Edit | My Note — AI Matrx"
❌  "Agents Build — AI Matrx"      ← all Agents tabs start with "Agents" — unreadable
❌  "Agents | AI Matrx"            ← redundant brand in middle position

Never append | AI Matrx inside the helpers — the root layout template does it. The helpers only set the %s portion of "%s — AI Matrx".


System-Route Color Families

Three route trees have a fixed color in utils/favicon-utils.ts. The color is locked — the letter is always unique per subroute.

Route treeColorFallback letterRule
/demo, /demos, /component-demo, /p/demo#ca8a04 warm yellowpath-derivedEvery demo route MUST pass its own unique letter
/tests, /beta, /experimental#65a30d lime greenpath-derivedEvery test route MUST pass its own unique letter
/administration, /admin#111827 near-blackpath-derivedReuse the mirrored feature's letter; admin-only pages get a clean 2-char code

The point: 20 yellow tabs in one browser window must each show a different 2-char badge so you can tell them apart. The color tells you "this is a demo tab" and the letter tells you which demo.

✅  Yellow "GH" = Glass Header demo
✅  Yellow "AC" = Accordion demo
✅  Yellow "SB" = Sortable demo
❌  Yellow "De" × 20 tabs = completely useless

Do not add favicon to nav entries for these paths — the system color is applied automatically. Just always pass letter in the metadata helpers.


Favicon Design Rules for Primary Routes

🚨 THE TWO HARD RULES (Arman, 2026-09-18 / 2026-09-21)

1. A badge is NEVER more than 2 characters. At 16px a third glyph makes the tile an unreadable smear. 1 char is fine when a route is alone in its space; 3 is never fine. 132 three-character codes existed on 2026-09-21 and all of them were collapsed. pnpm check:favicon-letters fails on any letter over two characters.

2. Uniqueness is PER COLOUR, not global. "Since the color is what sets them apart." /administration/agents wears the same AG as /agents — in near-black instead of rose. An admin page that mirrors a real feature reuses that feature's exact code; it never gets an A-for-Admin prefix, because the black tile already says admin. Two routes only collide when they resolve to the SAME colour and neither is an ancestor of the other.

Rule 2 is what makes rule 1 possible. A rigid "every page in this family starts with the family's letter" scheme runs out of room past ~26 sub-pages — so drop that constraint. The colour carries the family; the two letters only need to be a distinct, roughly mnemonic code for that page within its colour. /agents uses AG, AB, AH, AP, LA, SH, SU, WI, CP, BT, BM, BR, BS, BP, TL, BU, BV, NB, NC, NI, NT, NG, NM, SX — no shared first letter, no collisions, every one readable.

The emoji escape hatch

FaviconConfig carries emoji?: string, and every helper threads it (createRouteMetadata({ emoji }), createDynamicRouteMetadata, getRouteFavicon(path, letter, emoji), generateFaviconMetadata(path, meta, letter, emoji)). An emoji replaces the letters entirely and is exempt from the 2-character cap.

Use it for the handful of pages that do not reduce to two readable characters — Launchpad is the worked example: "LP" says nothing, "UL" said less, and a rocket says it instantly.

// constants/favicon-route-data.ts
{ href: "/launchpad", favicon: { color: CORE_HUB_COLOR, emoji: "🚀" } },
// app/(admin)/administration/launchpad/page.tsx — same rocket, admin's black tile
export const metadata = createRouteMetadata("/administration", {
  titlePrefix: "Launchpad",
  title: "Administration",
  emoji: "🚀",
});

It is an escape hatch, not the new default. Two letters are the norm; reach for an emoji when they genuinely read as nothing. /dashboard stays Db — that reads fine.

2-Letter Format (Required for routes with 7+ subroutes)

All primary routes use 2-letter favicon codes. The pattern:

  • First letter = section initial
  • Second letter = differentiator (avoids collision)
  • Color = unique, high-contrast, not used by any other route

Current primary route assignments (copy this table when adding new routes):

RouteLetterHex ColorNotes
/launchpad🚀#f97316Core-hub orange — emoji, not letters
/dashboardDb#f97316Core-hub orange (CORE_HUB_COLOR)
/agentsAG#f43f5eRose red (AGENTS_COLOR)
/agents/[id]/buildAB#f43f5eAgent Builder — inherits the family
/appletsAA#059669Dark emerald
/applets/manage/[id]/runAR#059669Agent Runner
/chatC#2563ebDeep blue (CHAT_COLOR)
/notesN#eab308Docs yellow (DOCS_COLOR)
/documentsDO#eab308Docs yellow (DOCS_COLOR)
/markdown-studioMD#eab308Docs yellow (DOCS_COLOR)
/dataDA#0891b2Sheets cyan (SHEETS_COLOR)
/filesF#0891b2Sheets cyan (SHEETS_COLOR)
/workbooksWB#0891b2Sheets cyan (SHEETS_COLOR)
/workflowsWF#6d28d9Violet (WORKFLOWS_COLOR)
/marketingMk#15803dGreen (MARKETING_COLOR)
/tasksT#16a34aGreen
/projectsP#4f46e5Indigo
/transcriptsTR#9333eaPurple-600
/artifactsAF#78716cStone-500
/scraperSC#3730a3Indigo-800
/sandboxSB#c2410cOrange-700
/messagesMS#db2777Pink-600
/settingsST#475569Slate-600
/demo/*De#ca8a04System override
/tests/*Tx#65a30dSystem override
/administration/*(twin)#111827System override — reuses the mirrored feature's code

The registry (constants/favicon-route-data.ts) is the authority — this table is the shape of it, not a copy of all ~200 entries. Read the file before adding one.

The colour taxonomy (Arman, 2026-09-18)

"Red: Agents. Blue: Chat. Yellow: notes, docs, etc. Light blue: Data, sheets, etc. Purple: Workflows, etc. Green: Marketing. Need a good color for admin — maybe black or gray."

The families are named constants exported from constants/favicon-route-data.ts. A route in a family references the constant; it never repeats the hex.

FamilyConstantHexMembers today
AgentsAGENTS_COLOR#f43f5e/agents and its whole subtree
ChatCHAT_COLOR#2563eb/chat
Notes / docsDOCS_COLOR#eab308/notes, /documents, /markdown-studio
Data / sheetsSHEETS_COLOR#0891b2/data, /files, /workbooks
WorkflowsWORKFLOWS_COLOR#6d28d9/workflows, /legacy/workflows
MarketingMARKETING_COLOR#15803d/marketing
AdministrationADMIN_COLOR#111827LOCKED for /administration, /admin
Generic core hubCORE_HUB_COLOR#f97316/launchpad, /dashboard

Two choices worth knowing before you second-guess them:

  • Docs yellow #eab308 is deliberately not the demo mustard #ca8a04. The demo colour is LOCKED, so a docs tab and a demo tab would otherwise be the same badge; #eab308 is brighter and more saturated and reads apart at 16px.
  • The core hub is orange. Red, blue, yellow, cyan, violet, green and near-black are all spoken for, so orange is the one primary hue left that says "front door, not a feature" — it is not Chat's blue and not Data's cyan.

A route that is not in a family keeps its own curated colour. A family is a statement about kinship, not a licence to repaint the registry.

Color Selection Rules

  • Never reuse a color already in this table
  • Colors that look alike at 16px: avoid #6366f1 near #4f46e5, #7c3aed near #6d28d9
  • Prefer distinct hue families — if Agents is rose, the next red-family route should be orange or pink, not another rose
  • High saturation helps at small sizes — pastels disappear

Step 1: Register in favicon-route-data.ts

{
  label: "My Feature",
  href: "/my-feature",
  favicon: { color: "#10b981", letter: "Mf" }, // unique color + 2-char letter
  ...
}

The favicon lookup is prefix-based — /my-feature/[id]/build inherits /my-feature's favicon automatically. Always pass the root path to the helpers.


Step 2A: Static Route Layout

// app/(a)/my-feature/layout.tsx  ← root section layout
import { createRouteMetadata } from "@/utils/route-metadata";

export const metadata = createRouteMetadata("/my-feature", {
  title: "My Feature",
  description: "One-line description",
  additionalMetadata: { keywords: ["keyword1", "keyword2"] },
  // letter not needed — comes from favicon-route-data.ts entry
});
// app/(a)/my-feature/[id]/build/layout.tsx  ← sub-page layout, unique badge
export const metadata = createRouteMetadata("/my-feature", {
  titlePrefix: "Build", // ← goes FIRST in the tab
  title: "My Feature",
  description: "Build and configure a my-feature item",
  letter: "MFB", // "My Feature Build" — unique across open tabs
});
// Tab: "Build | My Feature — AI Matrx"
// app/(authenticated)/demo/component-demo/glass-header/layout.tsx
export const metadata = createRouteMetadata("/demo", {
  titlePrefix: "Glass Header",
  title: "Demo",
  description: "Glass header component demo",
  letter: "GH", // REQUIRED — unique per demo; yellow color applied automatically
});
// Tab: "Glass Header | Demo — AI Matrx"  +  yellow "GH" favicon

Step 2B: Dynamic [id] Route Layout

// app/(a)/my-feature/[id]/layout.tsx
import { createDynamicRouteMetadata } from "@/utils/route-metadata";

export async function generateMetadata({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const item = await getItem(id);
  return createDynamicRouteMetadata("/my-feature", {
    title: item.name, // fetched name
    description: item.description?.slice(0, 120), // max ~120 chars
    // letter optional for primary routes — comes from nav-links.tsx
  });
}

// Sub-page with action context — specific word first, unique badge:
return createDynamicRouteMetadata("/agents", {
  titlePrefix: "Build",
  title: agent.name,
  description: `Configure ${agent.name}`,
  letter: "AB", // "Agent Builder" tab — unique vs "AR" (Agent Runner)
  ogImage: agent.coverUrl, // optional per-entity OG image
});

Step 3: Sub-pages — inherit, UNLESS they are tabs

A genuinely single-purpose sub-page may inherit its section's title and badge from the nearest layout that exports metadata or generateMetadata.

🚨 A TAB never inherits. When a layout is a tab shell — a tab bar, sub-nav or switcher over sibling routes a person moves between — every sibling names itself. On 2026-09-30 /administration/users/agent-review read "Users & Access": the shell exported one metadata and all fifteen tabs under it (Agent Review, Admins, Invitations, Usage…) inherited it, so fifteen open browser tabs were indistinguishable. The shell's own index page (users/page.tsx) keeps the section title — it is the front door, not a tab.

Static section title — a metadata-only layout.tsx beside the tab's page (a page is often "use client" and cannot export metadata):

// app/(admin)/administration/users/agent-review/layout.tsx
export const metadata = createRouteMetadata("/administration", {
  titlePrefix: "Agent Review",   // the tab's own name, as the tab bar says it
  title: "Users & Access",       // the shell's name — category last
  letter: "AE",                  // 2 chars, free within the colour family
});
export default function Layout({ children }: { children: React.ReactNode }) {
  return children;
}

Record shell (the section title is the record's name, fetched by the shell's generateMetadata — /research/topics/[topicId]/*, /organizations/[orgId]/*) — use createTabMetadata. It reads the parent's ALREADY-RESOLVED title (Next's second generateMetadata argument) and puts the tab's name in front, so the record's name survives and nothing is fetched twice:

// app/(core)/research/topics/[topicId]/sources/layout.tsx
export const generateMetadata = createTabMetadata("/research", {
  titlePrefix: "Sources",   // → "Sources | <topic name>"
  letter: "SO",
});

A module that titles itself from the request pathname through one resolver (Marketing's getMarketingRouteMetadata) adds the tab's identity to that resolver instead — never a second source of titles.

Guard: pnpm check:tab-shell-titles (:self-test proves it red then green). It evaluates each sibling's real title — static helpers through the shipping createRouteMetadata, createTabMetadata against its resolved parent, pathname resolvers by calling them — and fails when two siblings of one shell resolve to the same title. Redirect-only pages own no tab and are skipped. A deliberate pair is marked in the shell with // tab-shell-title-ok: <reason>.


Administration Layouts

Admin layouts use createRouteMetadata("/administration", ...). The deep-indigo color is applied automatically. Always pass a unique letter so 10+ admin tabs are distinguishable.

// app/(authenticated)/(admin-auth)/administration/schema-manager/layout.tsx
export const metadata = createRouteMetadata("/administration", {
  title: "Schema Manager",
  description: "...",
  letter: "SM", // REQUIRED — unique across all open admin tabs
});
// Tab: "Schema Manager — AI Matrx"  +  deep-indigo "SM" favicon

Demo & Test Layouts

Same rule — color is automatic, letter is required and must be unique per route.

// Accordion demo
export const metadata = createRouteMetadata("/demo", {
  titlePrefix: "Accordion",
  title: "Demo",
  description: "Accordion component demo",
  letter: "AC", // unique — no other demo should use "AC"
});

// Sortable demo
export const metadata = createRouteMetadata("/demo", {
  titlePrefix: "Sortable",
  title: "Demo",
  description: "Drag and sort component demo",
  letter: "So", // different from "AC", "GH", etc.
});

// Test route
export const metadata = createRouteMetadata("/tests", {
  title: "Form Tests",
  description: "...",
  letter: "FT", // unique across test tabs
});

Route Discovery System Integration

When RouteIndexPage is used for a section index page, it automatically:

  1. Resolves the favicon for the basePath
  2. Renders the favicon badge next to the page title
  3. Uses the favicon color as a left-border accent on group cards in GroupedCardsDisplay

No extra code needed — pass basePath="/my-feature" and it works.

See .claude/skills/route-discovery-system/SKILL.md for full RouteIndexPage usage.


Checklist for a New Route

- [ ] favicon entry added to favicon-route-data.ts with a UNIQUE color (or a family constant)
- [ ] letter is 1–2 chars — NEVER 3 — and free within that route's COLOUR family
- [ ] an admin page that mirrors a real feature reuses that feature's exact code
- [ ] a page that will not reduce to 2 readable characters uses `emoji` instead
- [ ] `pnpm check:favicon-letters` is green
- [ ] color confirmed not already in the table above
- [ ] top-level layout exports createRouteMetadata("/my-route", { title, description })
- [ ] sub-page layouts use titlePrefix for specific-word-first tab titles
- [ ] dynamic [id] layout uses generateMetadata + createDynamicRouteMetadata
- [ ] description is ≤120 chars for dynamic routes
- [ ] no manual | AI Matrx appended in title — root template handles it
- [ ] SCAN NEARBY ROUTES in the same directory — they likely also need metadata

"Scan nearby routes" — Always do this

When adding metadata to one route, check its siblings:

  • Look at all layout.tsx files in the same parent directory
  • Look at neighboring feature folders at the same level
  • Any layout missing createRouteMetadata or generateMetadata is missing its favicon

Escape Hatches

Favicon only, no other metadata:

import { getRouteFavicon } from "@/utils/route-metadata";
export const metadata = getRouteFavicon("/my-feature");

Custom favicon for a route not in favicon-route-data.ts:

import { createCustomFaviconMetadata } from "@/utils/favicon-utils";
export const metadata = createCustomFaviconMetadata(
  { color: "#f97316", letter: "Xp" },
  { title: "Special Route", description: "..." },
);

Common Mistakes

MistakeCorrect approach
title: "Agents Build"titlePrefix: "Build", title: "Agents"
title: "My Route | AI Matrx"title: "My Route" (root template adds brand)
Passing /agents/123/build to helpersAlways pass root: /agents
favicon: { color, letter } on admin nav entryOmit it — system color covers all /administration/*
Omitting letter on a demo/test/admin layoutEvery route in these families MUST have a unique letter
Reusing the same letter across two demo routesDefeats the whole purpose — scan the nearby layouts first
Picking a color already in the table for primary routesCheck the full table above first
1-char letter on a high-traffic routeUse 2-char — more visually distinct at 16px
Same first letter with ambiguous secondPb vs Pa works; Pb vs Pc is risky — pick visually distinct shapes
Thinking the system letter fallback is acceptableThe path-derived fallback is a safety net only — always pass an explicit letter
A 3-character code (BTU, ADA, WRL)Two characters, or an emoji. There is no third option — the guard fails the run
AD/A… prefixing an admin page to mark it as adminReuse the real feature's code; the near-black tile already says admin
Making a letter globally unique across all ~400 routesOnly same-COLOUR routes can collide — a core tab and an admin tab are two tiles
Guessing that your new letter is freeRun pnpm check:favicon-letters — it judges length and same-colour collisions
Repeating a family hex instead of its constantImport DOCS_COLOR / SHEETS_COLOR / … from constants/favicon-route-data.ts

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Admin debug system wiring for the floating AdminIndicator's live debug data and Copy Full Context. Use when adding debug visibility to a route or feature, wiring useDebugContext or the debug panel, capturing console errors, or enabling the copy-context workflow for a page.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Compact two-icon Copy / Copy-for-AI controls (components/agent-copy). Use when adding copy buttons to a row, card, list, or record; merging duplicate Copy/JSON/AI controls; continuing the copy rollout; or writing a Copy-for-AI payload. NOT for markdown content actions (use rich-document-actions).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Disclosing a surface's existing fixed AI jobs in the shell's top Agents menu. Use when a page, panel, overlay, or window already runs a mandate behind a button, assist, automatic action, or mode; when the agent-disclosure guard names a file; or during a surface check.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

The watch-fix-rerun method for making an existing platform agent or automated process efficient and correct: baseline its ledger, run one unit yourself, fix the class behind every wasted call, rerun, record. Use when asked to improve, optimize, watch, or 'make efficient' an agent, a sandbox session, a sync job, or any recurring automated process.

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

Redux state for agent execution and firing agent shortcuts. Use when editing features/agents/redux/, building agent UI, creating a conversation, touching assembleRequest or NDJSON stream state, wiring agent-state selectors, adding a per-conversation capability, or triggering a shortcut from a button, menu, or code (useShortcutTrigger, launchAgentExecution).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

The provision for a call site: the exhaustive menu of values that place in the code can realistically produce. Use before creating or fixing a mandate or agent that reasons about more than its own input, when deciding whether an agent can answer at all with what it is sent, or when its output 'looks right' but nobody checked it could be. NOT for building the agent (use create-agent).

日本語の概要は準備中です。原文の説明を表示しています。

armanisadeghi/ai-matrx32026年10月13日 更新

armanisadeghi のスキルをすべて見る

このスキルの問題を報告する