| 1 | --- |
| 2 | name: og-studio |
| 3 | description: Generate Open Graph images for shareable pages of a web app, either as committed static assets or framework-native metadata routes such as Next.js `opengraph-image.tsx`, and wire page metadata to the new assets. Use when replacing existing og:image or twitter:image files, adding route-level social preview images, or migrating a repo to structured Open Graph image handling. Triggers on Open Graph, og:image, Twitter card images, opengraph-image.png, opengraph-image.tsx, social preview images, metadata images, and page preview assets. |
| 4 | --- |
| 5 | |
| 6 | # Open Graph Images |
| 7 | |
| 8 | ## Overview |
| 9 | |
| 10 | Create social preview images for real application pages, either as committed static assets or framework-native code routes, and wire the app so each page resolves to the new asset. The output is committed repo state, not browser downloads or temporary files. |
| 11 | |
| 12 | ## Core Principle |
| 13 | |
| 14 | **Generate into the repository and the framework's metadata conventions, not the browser.** The task is only complete when the final files or route handlers live in framework-correct paths and the application metadata resolves to them. |
| 15 | |
| 16 | Default to a **review-first workflow** when the repo does not already have a deterministic OG pipeline. Let the user preview and approve cards before wiring them into metadata. |
| 17 | |
| 18 | ## Step 1: Gather the Minimum Inputs |
| 19 | |
| 20 | Before changing code, determine these inputs from the user or the repo: |
| 21 | |
| 22 | 1. **Brand anchors**: logo/icon path, primary brand colors, preferred font if the app already has one |
| 23 | 2. **Page scope**: all public pages or a subset |
| 24 | 3. **Style direction**: minimal/editorial, bold gradient, dark, playful, enterprise, or "match the site" |
| 25 | 4. **Copy source**: page titles, headings, and value props already present in the repo |
| 26 | 5. **Current system**: whether the repo already has OG/Twitter assets or metadata wiring |
| 27 | |
| 28 | If 1-3 cannot be inferred reliably, ask one concise question. Everything else should come from repository inspection. |
| 29 | |
| 30 | ## Step 2: Inventory Routes and Existing OG Wiring |
| 31 | |
| 32 | Use the bundled helper first: |
| 33 | |
| 34 | ```bash |
| 35 | python3 scripts/detect_og_targets.py /path/to/repo > /tmp/og-targets.json |
| 36 | ``` |
| 37 | |
| 38 | The helper reports: |
| 39 | |
| 40 | - framework guess |
| 41 | - route candidates |
| 42 | - existing `opengraph-image.*` and `twitter-image.*` files |
| 43 | - `public/og/*` assets |
| 44 | - files that already contain OG/Twitter metadata wiring |
| 45 | - the recommended workflow |
| 46 | - the most likely output strategy |
| 47 | |
| 48 | Treat the script output as a starting point, not a substitute for judgment. Manually prune: |
| 49 | |
| 50 | - admin/settings/dashboard internals |
| 51 | - auth/login/logout/callback routes |
| 52 | - API routes |
| 53 | - error/loading/template/not-found routes |
| 54 | - large dynamic sets unless the framework can support a code-generated metadata route cleanly |
| 55 | |
| 56 | Default target set: |
| 57 | |
| 58 | - homepage |
| 59 | - pricing |
| 60 | - product/feature pages |
| 61 | - docs landing pages and major docs sections |
| 62 | - blog index and high-value evergreen pages |
| 63 | |
| 64 | ### Shell Hygiene for Real Route Paths |
| 65 | |
| 66 | Many repos use Next App Router route groups like `(info)` and dynamic segments like `[slug]`. In `zsh`, unquoted paths containing `(`, `)`, `[`, or `]` can fail before the command even runs. |
| 67 | |
| 68 | Always quote these paths in shell commands: |
| 69 | |
| 70 | ```bash |
| 71 | sed -n '1,80p' 'app/(info)/pricing/page.tsx' |
| 72 | sed -n '1,120p' 'app/(content)/blog/[slug]/page.tsx' |
| 73 | rg -n "createPageMetadata" 'app/(info)' 'app/(content)' |
| 74 | ``` |
| 75 | |
| 76 | Do not treat `zsh: no matches found` as a repository problem until you have retried with quoted paths. |
| 77 | |
| 78 | ## Step 3: Choose the Integration Strategy |
| 79 | |
| 80 | ### Default Recommendation Matrix |
| 81 | |
| 82 | Use this as the default decision table unless the user explicitly asks for a one-shot auto-wiring flow: |
| 83 | |
| 84 | | Repo shape | Default workflow | Static page connection | Dynamic page connection | |
| 85 | |------------|------------------|------------------------|-------------------------| |
| 86 | | Next App Router with only stable public pages | Review hub first | `Import` / `Import All` into the existing static system | N/A | |
| 87 | | Next App Router with stable pages plus slug routes | Review hub for stable pages plus dynamic route apply | `Import` / `Import All` | `Apply Dynamic Route` writes `opengraph-image.tsx` | |
| 88 | | Next Pages Router / Astro / Remix / Vite | Review hub first | `Import` / `Import All` into `public/og` or existing metadata helper | Use helper or fallback unless the framework has an equivalent dynamic metadata route | |
| 89 | | Repo already has a mature OG pipeline | Extend existing system | Preserve the current import/wiring shape | Preserve the current dynamic route/helper shape | |
| 90 | |
| 91 | ### Preferred UX: Review Hub Before Wiring |
| 92 | |
| 93 | For most repos, especially when style and copy may still change, prefer building a local-only **OG Hub** first instead of immediately wiring generated images into metadata. |
| 94 | |
| 95 | The hub should: |
| 96 | |
| 97 | - display every target card in one grid |
| 98 | - support per-target preview at thumbnail size and full-size view |
| 99 | - support per-target `Import` buttons |
| 100 | - support `Import All` |
| 101 | - keep generated outputs staged until the user imports them |
| 102 | - make it obvious which targets are already imported versus still staged |
| 103 | |
| 104 | Use this pattern for: |
| 105 | |
| 106 | - stable marketing pages |
| 107 | - docs landing pages |
| 108 | - blog index and other small public route sets |
| 109 | |
| 110 | Do not force auto-wiring unless the user explicitly asks for a one-shot workflow or the repo already has an approved deterministic generator. |
| 111 | |
| 112 | ### Static Import Semantics |
| 113 | |
| 114 | For static asset systems, `Import` means: |
| 115 | |
| 116 | 1. render or capture the selected card |
| 117 | 2. write it to the final repo path |
| 118 | 3. update metadata references if they are not already wired |
| 119 | 4. mark the target as imported in the hub UI |
| 120 | |
| 121 | `Import All` does the same for every staged target. |
| 122 | |
| 123 | If the user wants to tweak copy, accent, badges, or artwork before wiring, keep those edits inside the hub state or the underlying target config until import is confirmed. |
| 124 | |
| 125 | ### Dynamic Route Semantics |
| 126 | |
| 127 | For dynamic Next App Router systems using `opengraph-image.tsx`, the hub should not behave like one-image-per-slug import. |
| 128 | |
| 129 | Instead, the hub should: |
| 130 | |
| 131 | - preview the shared dynamic card template |
| 132 | - let the user switch among sample slugs |
| 133 | - let the user edit the template or route-level rendering logic |
| 134 | - offer an `Apply Dynamic Route` action that writes or updates `opengraph-image.tsx` |
| 135 | - optionally offer `Apply All Static Imports` separately for stable non-dynamic pages |
| 136 | |
| 137 | For dynamic routes, the “connection” step is wiring the route handler once, not importing hundreds of generated PNG files. |
| 138 | |
| 139 | ### Exact Hub Architecture |
| 140 | |
| 141 | When implementing the review-first flow, prefer this exact layout unless the repo already has a better equivalent: |
| 142 | |
| 143 | ```text |
| 144 | src/app/og-studio/page.tsx # local-only shell route |
| 145 | src/app/og-studio/OgStudioClientPage.tsx # grid, preview, import controls |
| 146 | src/app/og-studio/client-utils.ts # html-to-image capture helpers |
| 147 | src/app/og-studio/[key]/page.tsx # local-only full preview route |
| 148 | src/app/og-studio/[key]/OgStudioPreviewClient.tsx |
| 149 | src/app/api/og/import/route.ts # POST-only import endpoint |
| 150 | src/lib/og/targets.ts # canonical OG definitions and imported-state lookup |
| 151 | src/lib/og/card.tsx # browser-safe shared card renderer |
| 152 | src/lib/og/importer.ts # writes PNG buffers into final repo paths |
| 153 | src/lib/og/renderers.tsx # optional next/og helpers for dynamic routes |
| 154 | ``` |
| 155 | |
| 156 | Use these responsibilities: |
| 157 | |
| 158 | - `page.tsx`: local-only gate and lightweight shell that groups targets for the hub |
| 159 | - `OgStudioClientPage.tsx`: toolbar, grid, staged/imported status, per-card `Preview` and `Import` |
| 160 | - `client-utils.ts`: wait for fonts and images, capture the exact DOM preview with `html-to-image`, POST the PNG to the server |
| 161 | - `app/api/og/import/route.ts`: accept `{ key, dataUrl }` and write the browser-captured PNG into the repo |
| 162 | - `[key]/page.tsx` and `OgStudioPreviewClient.tsx`: local-only full-size preview route for one target |
| 163 | - `targets.ts`: which routes exist, which output paths they map to, and whether each target is already imported |
| 164 | - `card.tsx`: canonical browser-safe card tree used by the studio preview |
| 165 | - `importer.ts`: final file writes only; no second renderer for static imports |
| 166 | - `renderers.tsx`: optional `ImageResponse` helpers for dynamic `opengraph-image.tsx` routes, kept separate from static studio imports |
| 167 | |
| 168 | For static imports, do not use server actions or a second image renderer by default. Preview in the browser, capture that exact preview, and write the PNG through a local POST route. |
| 169 | |
| 170 | ### Strategy A: Next.js App Router with Route-Local Static Assets |
| 171 | |
| 172 | Use this when the repo has `app/` or `src/app/` routes. |
| 173 | |
| 174 | Write static assets next to the route segment: |
| 175 | |
| 176 | - root route: `app/opengraph-image.png` |
| 177 | - nested route: `app/pricing/opengraph-image.png` |
| 178 | - add `twitter-image.png` only when the repo already uses route-local Twitter assets or the user wants separate Twitter variants |
| 179 | |
| 180 | Prefer replacing existing route-local assets in place. |
| 181 | |
| 182 | ### Strategy B: Next.js App Router Dynamic Metadata Routes |
| 183 | |
| 184 | Use this for dynamic content collections such as `/blog/[slug]`, `/compare/[slug]`, `/docs/[...slug]`, or any route where each record should get its own OG card automatically. |
| 185 | |
| 186 | Prefer this over generating and committing one PNG per slug. |
| 187 | |
| 188 | Write route-local metadata files inside the dynamic segment: |
| 189 | |
| 190 | - `app/blog/[slug]/opengraph-image.tsx` |
| 191 | - `app/blog/[slug]/twitter-image.tsx` only if the repo already expects separate Twitter variants |
| 192 | |
| 193 | Use `ImageResponse` from `next/og` and the same content loader or data source the page already uses. The generated image should pull the real slug title/subtitle rather than duplicating another source of truth. |
| 194 | |
| 195 | Next.js behavior to rely on: |
| 196 | |
| 197 | - `opengraph-image.tsx` is a special metadata route and updates the route segment automatically |
| 198 | - generated images are statically optimized by default unless they use Dynamic APIs or uncached data |
| 199 | - the more specific segment wins over parent metadata files |
| 200 | |
| 201 | For mixed systems, use: |
| 202 | |
| 203 | - static assets for stable marketing pages |
| 204 | - `opengraph-image.tsx` for high-volume dynamic records |
| 205 | - one shared fallback only when unique per-record cards are not worth the complexity |
| 206 | |
| 207 | ### Strategy C: Static Assets Under `public/og` |
| 208 | |
| 209 | Use this for Next.js pages router, Astro, Remix, Vite SPAs, and custom React apps that wire metadata in code. |
| 210 | |
| 211 | Write stable files such as: |
| 212 | |
| 213 | - `public/og/home.png` |
| 214 | - `public/og/pricing.png` |
| 215 | - `public/og/docs-getting-started.png` |
| 216 | |
| 217 | Then point each page's `og:image` and `twitter:image` fields to those assets. |
| 218 | |
| 219 | ### Strategy D: Extend the Existing OG System |
| 220 | |
| 221 | If the repo already has a clear OG image system, extend it instead of inventing a second one. |
| 222 | |
| 223 | Examples: |
| 224 | |
| 225 | - existing `public/og/*.png` plus SEO helper: keep that structure and swap in new files |
| 226 | - existing root-level `public/og-image.png` plus metadata helper: treat it as the shared fallback and extend around it |
| 227 | - existing route-local `opengraph-image.png`: regenerate those exact files |
| 228 | - existing route-local `opengraph-image.tsx`: keep that route-local dynamic model and improve the card output instead of replacing it with static files |
| 229 | - existing `getSeo()` / `buildMetadata()` helper: feed new paths through the helper |
| 230 | |
| 231 | **Never** add both route-local assets and a new `public/og` structure for the same pages unless the repo already deliberately uses both. |
| 232 | |
| 233 | ## Step 4: Build the Generator Inside the Repo |
| 234 | |
| 235 | Prefer a local-only OG studio inside the app when the stack is React-, Next-, or Astro-based and visual consistency matters. |
| 236 | |
| 237 | Recommended structure for static asset generation and review-first flows: |
| 238 | |
| 239 | ```text |
| 240 | src/app/og-studio/page.tsx |
| 241 | src/app/og-studio/OgStudioClientPage.tsx |
| 242 | src/app/og-studio/client-utils.ts |
| 243 | src/app/og-studio/[key]/page.tsx |
| 244 | src/app/og-studio/[key]/OgStudioPreviewClient.tsx |
| 245 | src/app/api/og/import/route.ts |
| 246 | src/lib/og/card.tsx |
| 247 | src/lib/og/importer.ts |
| 248 | src/lib/og/targets.ts |
| 249 | ``` |
| 250 | |
| 251 | Rules: |
| 252 | |
| 253 | - In Next App Router, do not put a routable studio under a leading-underscore folder such as `_og-studio` or `__og-studio`. Underscore-prefixed folders are private and opt out of routing. |
| 254 | - Keep the generator route local-only unless the user explicitly asks to publish it. |
| 255 | - Use one fixed canvas size: `1200x630`. |
| 256 | - Use one shared design system with a few layout variants instead of one-off templates. |
| 257 | - Pull copy from real page content whenever possible. |
| 258 | - Write images directly to their final repo paths. |
| 259 | - If the studio is local-only, gate it deliberately with host checks or environment checks instead of relying on a private folder name. |
| 260 | - If using a hub, keep the route thin and move the interactive grid and import interactions into `OgStudioClientPage.tsx`. |
| 261 | - For static imports, keep the preview renderer and the final imported PNG on the same path by capturing the real browser preview node. |
| 262 | |
| 263 | ### Hub Interaction Model |
| 264 | |
| 265 | When building a review hub, prefer this UI model: |
| 266 | |
| 267 | - a toolbar with `Import All`, imported count, and a short status message |
| 268 | - a responsive grid of OG cards |
| 269 | - each card shows route label, output path, and current status: `staged` or `imported` |
| 270 | - each card offers `Preview` and `Import` or `Re-import` |
| 271 | - preview uses the real 1200x630 browser render, scaled down in the grid |
| 272 | - imported status comes from final file existence and metadata wiring, with local optimistic UI after import |
| 273 | |
| 274 | Do not add a separate `Generate` or `Regenerate` lane by default. The staged preview is the live browser render, and `Import` writes that exact preview into the repo. |
| 275 | |
| 276 | ### Import Route Contract |
| 277 | |
| 278 | Implement the static import path with a local POST route, not a GET helper: |
| 279 | |
| 280 | - `POST /api/og/import` |
| 281 | - request body: `{ key: string, dataUrl: string }` |
| 282 | - response body: `{ imported: number, results: [{ key, outputPath, absolutePath }] }` |
| 283 | |
| 284 | Static import semantics: |
| 285 | |
| 286 | - `Import(targetKey)`: capture the selected browser preview, POST it, write or replace the final PNG, and keep metadata aligned |
| 287 | - `ImportAll()`: capture and import every staged static target in sequence |
| 288 | |
| 289 | If the repo also needs dynamic-route application, expose that through a separate action or route. Do not mix static browser-capture imports and dynamic `opengraph-image.tsx` application into the same handler. |
| 290 | |
| 291 | ### Preferred Capture Method |
| 292 | |
| 293 | For a static OG studio, prefer browser DOM capture of the exact preview node with `html-to-image`. |
| 294 | |
| 295 | Typical split: |
| 296 | |
| 297 | - preview rendering in the browser via React |
| 298 | - final import writing through a local POST route |
| 299 | |
| 300 | Typical capture pattern: |
| 301 | |
| 302 | ```ts |
| 303 | await document.fonts.ready; |
| 304 | const dataUrl = await toPng(node, { |
| 305 | width: 1200, |
| 306 | height: 630, |
| 307 | pixelRatio: 1, |
| 308 | cacheBust: true, |
| 309 | style: { |
| 310 | transform: "none", |
| 311 | transformOrigin: "top left", |
| 312 | }, |
| 313 | }); |
| 314 | await fetch("/api/og/import", { |
| 315 | method: "POST", |
| 316 | headers: { "Content-Type": "application/json" }, |
| 317 | body: JSON.stringify({ key, dataUrl }), |
| 318 | }); |
| 319 | ``` |
| 320 | |
| 321 | Before capture: |
| 322 | |
| 323 | - wait for `document.fonts.ready` |
| 324 | - wait for every `<img>` inside the preview node to finish loading |
| 325 | - ensure each card renders at exactly `1200x630` |
| 326 | - if the in-grid preview is scaled down, override the transform during capture so the saved PNG is full size |
| 327 | - set an intentional background so the output never depends on transparency fallback |
| 328 | |
| 329 | Do not preview with one renderer and import with another for static targets. If the studio preview is browser-rendered, the imported PNG should come from that same browser render. |
| 330 | |
| 331 | Use Playwright only as a fallback for non-studio automation or batch jobs where no interactive review hub exists. |
| 332 | |
| 333 | ### Local Dev Server Hygiene |
| 334 | |
| 335 | If a generation script boots a local dev server: |
| 336 | |
| 337 | - use a configurable port such as `OG_PORT` |
| 338 | - if the environment blocks local port binding or dev-server startup, request the needed approval before changing the implementation |
| 339 | - check whether that port is already occupied before retrying a failed run |
| 340 | - avoid starting a second generator while the first one is still running |
| 341 | - if Next reports a stale dev lock, confirm the original process is dead before removing the lock file |
| 342 | - after an interrupted run, inspect active processes and lockfiles before declaring the workflow broken |
| 343 | |
| 344 | ## Step 5: Design Rules for the Cards |
| 345 | |
| 346 | - One page, one message |
| 347 | - Treat the image as a poster, not a page screenshot |
| 348 | - Keep text large and scannable at thumbnail size |
| 349 | - Reuse the app's visual language instead of inventing a parallel brand |
| 350 | - Use at most 2-4 layout variants across the entire set |
| 351 | |
| 352 | Good composition ingredients: |
| 353 | |
| 354 | - product mark or logo |
| 355 | - page label or section kicker |
| 356 | - one strong headline |
| 357 | - optional short subline |
| 358 | - restrained background gradient, pattern, or shape |
| 359 | |
| 360 | Typical framing by page type: |
| 361 | |
| 362 | - home: flagship promise |
| 363 | - pricing: straightforward pricing/sales framing |
| 364 | - feature page: one specific outcome |
| 365 | - docs: docs label plus section title |
| 366 | - blog/article: article title, optionally author/date only if it improves clarity |
| 367 | - dynamic content page: real record title first, optional taxonomy/author/date only if it helps the thumbnail scan faster |
| 368 | |
| 369 | Avoid: |
| 370 | |
| 371 | - tiny UI screenshots |
| 372 | - crowded grids |
| 373 | - multiple competing messages |
| 374 | - decorative noise that overpowers the text |
| 375 | |
| 376 | ## Step 6: Write Files to Final Paths |
| 377 | |
| 378 | Generate directories before capture. Do not route outputs through Downloads, Desktop, or temp folders unless a later script immediately moves them into place in the same change. |
| 379 | |
| 380 | Slug rules for `public/og`: |
| 381 | |
| 382 | - `/` -> `home.png` |
| 383 | - `/pricing` -> `pricing.png` |
| 384 | - `/docs/getting-started` -> `docs-getting-started.png` |
| 385 | |
| 386 | Rules for replacing existing assets: |
| 387 | |
| 388 | - overwrite in place when the current paths are already correct |
| 389 | - if filenames or structure change, update every reference in the same change |
| 390 | - remove obsolete duplicates only after verifying nothing points to them |
| 391 | |
| 392 | ## Step 7: Wire Metadata into the App |
| 393 | |
| 394 | If the project uses a review hub, wiring should happen at import time, not as soon as the preview exists. |
| 395 | |
| 396 | ### Next.js App Router |
| 397 | |
| 398 | If route-local `opengraph-image.png` is used, Next will usually pick it up automatically. Still inspect: |
| 399 | |
| 400 | - `metadata` |
| 401 | - `generateMetadata` |
| 402 | - parent layouts |
| 403 | - any hard-coded `openGraph.images` or `twitter.images` |
| 404 | |
| 405 | If those overrides still point to old assets, update or remove them so the route-local file wins. |
| 406 | |
| 407 | For dynamic segments in App Router, prefer `opengraph-image.tsx` over a static fallback when each slug should render its own card automatically. Keep the image route next to the dynamic page so the slug params and data loader stay local to the route segment. |
| 408 | |
| 409 | If a hub is present, the import/apply behavior should be split: |
| 410 | |
| 411 | - stable routes: `Import` writes the final asset and connects metadata |
| 412 | - dynamic routes: `Apply Dynamic Route` writes or updates `opengraph-image.tsx` |
| 413 | |
| 414 | ### Next.js Pages Router |
| 415 | |
| 416 | Update the shared SEO system or page-level metadata to point at `/og/...png`. |
| 417 | |
| 418 | Check: |
| 419 | |
| 420 | - `next-seo.config.*` |
| 421 | - shared `SEO` components |
| 422 | - `next/head` usage |
| 423 | - metadata helpers in `lib/seo*`, `utils/seo*`, or similar |
| 424 | |
| 425 | ### Astro / Remix / Vite / Custom React |
| 426 | |
| 427 | Find the abstraction that owns `<meta property="og:image">` and `<meta name="twitter:image">`. Only change image fields unless the user explicitly asks for broader SEO cleanup. |
| 428 | |
| 429 | ### Twitter Cards |
| 430 | |
| 431 | If the repo already uses Twitter card tags, keep them aligned with the new OG assets. Reuse the same PNG by default unless the repo already expects separate Twitter image files. |
| 432 | |
| 433 | ## Step 8: Replace Current OG Images Safely |
| 434 | |
| 435 | - Prefer in-place replacement over path churn |
| 436 | - If the current filenames are stable and already wired, regenerate those exact filenames |
| 437 | - If migrating to a cleaner structure, update all callers in the same patch |
| 438 | - Do not delete old files until reference search confirms zero callers |
| 439 | |
| 440 | Useful search: |
| 441 | |
| 442 | ```bash |
| 443 | rg -n "og:image|twitter:image|openGraph|twitter.*images|opengraph-image|/og/" /path/to/repo |
| 444 | ``` |
| 445 | |
| 446 | After changing metadata helpers or image variables, also search for stale symbol references and structured data blocks that still point at the old image: |
| 447 | |
| 448 | ```bash |
| 449 | rg -n "ogImageUrl|twitterImageUrl|ImageObject|image:" /path/to/repo |
| 450 | ``` |
| 451 | |
| 452 | ## Step 9: Verify Before Finishing |
| 453 | |
| 454 | 1. Confirm every target PNG exists at its final repo path |
| 455 | 2. Confirm every target page points to the new asset or inherits it correctly |
| 456 | 3. Search for stale OG/Twitter references |
| 457 | 4. Run the app and inspect one representative page per route family |
| 458 | 5. Verify rendered head output if the framework exposes it locally |
| 459 | 6. If App Router dynamic metadata routes were added, request at least one real slug and confirm the route returns an image successfully |
| 460 | 7. If metadata helpers were refactored, confirm JSON-LD or structured data blocks do not still reference the previous image variable |
| 461 | 8. Do not claim success if the only outputs exist in a temp directory or browser downloads |
| 462 | |
| 463 | For hub-based flows also verify: |
| 464 | |
| 465 | 9. imported cards are visually distinguished from staged cards |
| 466 | 10. `Import` updates only the selected target |
| 467 | 11. `Import All` updates every staged static target exactly once |
| 468 | 12. dynamic-route preview and static-import flows are not conflated in the same button |
| 469 | |
| 470 | ## Route Selection Rules |
| 471 | |
| 472 | Default include: |
| 473 | |
| 474 | - homepage |
| 475 | - pricing |
| 476 | - feature/product pages |
| 477 | - docs overview pages |
| 478 | - blog index and evergreen pages |
| 479 | - about/contact/legal only when clearly public and useful |
| 480 | |
| 481 | Default exclude: |
| 482 | |
| 483 | - API routes |
| 484 | - admin/settings/dashboard internals |
| 485 | - auth/login/logout/callback |
| 486 | - loading/template/error/not-found routes |
| 487 | - user-specific dynamic record pages |
| 488 | |
| 489 | Ask before including very large dynamic sets such as `/blog/[slug]`, `/docs/[...slug]`, or `/products/[id]` if the repo would need dozens or hundreds of committed assets. |
| 490 | |
| 491 | For Next.js App Router, do not default those routes to static asset fan-out. Prefer `opengraph-image.tsx` in the dynamic segment when the route data already exists and unique per-record cards are desired. |
| 492 | |
| 493 | ## Helper Script |
| 494 | |
| 495 | Use `scripts/detect_og_targets.py` from this skill folder to inventory: |
| 496 | |
| 497 | - framework family |
| 498 | - routing style |
| 499 | - candidate routes |
| 500 | - existing OG/Twitter assets |
| 501 | - metadata hint files |
| 502 | - recommended output strategy |
| 503 | |
| 504 | Treat its output as advisory. You still need to inspect the repo and choose the cleanest migration path. |
| 505 | |
| 506 | ## Reference Artifacts |
| 507 | |
| 508 | Use these bundled resources instead of re-inventing the same hub structure each time: |
| 509 | |
| 510 | - Next App Router hub guidance: [references/next-app-router-og-hub.md](references/next-app-router-og-hub.md) |
| 511 | - Next App Router template skeleton: `assets/next-app-router-og-hub/` |
| 512 | |
| 513 | The template asset pack includes: |
| 514 | |
| 515 | - `app/og-studio/page.tsx.example` |
| 516 | - `app/og-studio/OgStudioClientPage.tsx.example` |
| 517 | - `app/og-studio/client-utils.ts.example` |
| 518 | - `app/og-studio/[key]/page.tsx.example` |
| 519 | - `app/og-studio/[key]/OgStudioPreviewClient.tsx.example` |
| 520 | - `app/api/og/import/route.ts.example` |
| 521 | - `lib/og/card.tsx.example` |
| 522 | - `lib/og/importer.ts.example` |
| 523 | - `lib/og/targets.ts.example` |
| 524 | - `app/blog/[slug]/opengraph-image.tsx.example` |
| 525 | |
| 526 | Use the reference doc first to choose the right workflow. Copy or adapt template files only after confirming the target repo is Next App Router and the user wants the hub-first pattern. |
| 527 | These are intentionally stored as `*.example` files so this skill repository does not try to typecheck Next.js template code outside a Next.js app. Rename them back to their real filenames when copying into the target repo. |
| 528 | |
| 529 | ## Common Mistakes |
| 530 | |
| 531 | | Mistake | Fix | |
| 532 | |---------|-----| |
| 533 | | Generated files land in Downloads | Write directly to repo paths through the local import route; do not rely on browser downloads | |
| 534 | | A second OG system is added beside the first | Extend the existing wiring instead of duplicating it | |
| 535 | | Every route gets an image, including admin/auth/api | Filter to canonical public pages | |
| 536 | | The agent wires images immediately before the user can review them | Build an OG hub with staged previews, `Import`, and `Import All` as the default workflow | |
| 537 | | The preview looks right but the imported PNG looks different | Capture the real browser preview with `html-to-image` and POST that PNG to the server instead of re-rendering it through another engine | |
| 538 | | A hub treats dynamic `opengraph-image.tsx` routes like static PNG imports | Use preview plus `Apply Dynamic Route`; do not pretend every slug needs a committed file | |
| 539 | | `zsh: no matches found` on `(group)` or `[slug]` paths | Quote the path before assuming the file is missing | |
| 540 | | A Next studio route is placed under `_og-studio` or `__og-studio` and never resolves | Use `og-studio` or a route group plus a normal segment; underscore folders are private | |
| 541 | | Text is tiny or the design is screenshot-heavy | Treat the image as a poster, not a UI capture | |
| 542 | | `og:image` changes but `twitter:image` still points to the old file | Keep both tags aligned | |
| 543 | | Route-local Next assets exist but metadata overrides still win | Remove or update the overrides | |
| 544 | | A repo already has `public/og-image.png` and the agent misses it | Treat root-level public OG files as part of the existing system and extend them deliberately | |
| 545 | | Old `public/og` files remain after migration | Delete only after zero-reference verification | |
| 546 | |