← Back to the skills catalog

Skill Detail

Open Graph Images

stable

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.

14 files
View on GitHub

Install

npx skills add dqstartupbuild/skills/library/og-studio

README Preview

Overview

Open README

Open Graph Images Skill

A comprehensive toolset and guidance for generating, managing, and wiring OpenGraph (OG) images for web applications. This skill focuses on building high-quality, brand-consistent social preview images that are committed directly to your repository or served via framework-native dynamic routes.

🚀 Overview

This repository provides the infrastructure to:

  • Detect OG targets across various web frameworks (Next.js, Astro, Remix, etc.).
  • Generate static 1200x630 social images by capturing the exact browser preview used in the OG Studio.
  • Implement an "OG Studio" hub for reviewing and importing images before they go live.
  • Wire generated assets into the application's metadata systems (App Router, Pages Router, SEO helpers).

📂 Repository Structure

Below is a breakdown of every file in this repository and its role in the OG image generation workflow.

📄 Core Files

  • SKILL.md: The primary instruction manual for the Antigravity AI assistant. It contains the logic, decision matrices, and safety checks for performing OG image tasks.
  • README.md: This file! Provides a human-readable overview of the repository.

🛠️ Scripts

  • scripts/detect_og_targets.py: A Python utility that scans a target repository to identify frameworks, routes, existing OG assets, and SEO wiring. It recommends the best integration strategy.

📦 Assets (Templates & Examples)

These files are stored as .example files to avoid type-checking issues in this repository. They are intended to be copied and renamed in the target project.

OG Studio Hub (Next.js App Router)

The following files comprise a local-only "Studio" where users can preview and manage OG cards:

Dynamic Routes

📚 References

🏗️ Core Principles

  1. Repo-Native: Images should live in the repository (e.g., public/og/ or route-local opengraph-image.png), not as external hosted assets.
  2. Review-First: By default, build an OG Hub to let users approve designs and copy before they are wired into production metadata.
  3. Preview Parity: For static OG hubs, import the exact browser preview instead of re-rendering it through a second image engine.
  4. Poster Design: OG images should look like high-quality posters—large text, clear branding, and minimal clutter—not just page screenshots.

🛠️ Getting Started for Assistants

  1. Inventory: Run python3 scripts/detect_og_targets.py <path_to_repo> to understand the current state.
  2. Strategy: Consult the Recommendation Matrix in SKILL.md to choose between Static Imports or Dynamic Routes.
  3. Implement: Use the templates in assets/ to build the studio and generators.
  4. Verify: Confirm that the imported PNGs match the browser preview and that metadata points to the new assets.

SKILL.md

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