Files
ComfyUI_frontend/apps/website/scripts
Christian Byrne 35443e94f5 feat(website): SEO model pages — 207 models, FAQ JSON-LD, partner node support (#11892)
## Summary

- Adds programmatic SEO model pages at `/p/supported-models/[slug]` for
**207 models** auto-generated from `workflow_templates` (180 local + 27
partner nodes)
- 3-file architecture: `generated-models.json` (auto-generated, checked
in) + `model-metadata.ts` (editorial overrides) + `models.ts` (65-line
merger)
- Full JSON-LD per page: `SoftwareApplication` + `BreadcrumbList` +
`FAQPage` (targeting AI Overviews / People Also Ask)
- Partner node support: `directory: 'partner_nodes'` hides Download
button, shows VIEW TUTORIAL
- `generate-models.ts`: walks `workflow_templates` for local models +
`API_PROVIDER_MAP` for 30+ partner integrations (Kling, Meshy, Luma,
Runway, Stability AI, ByteDance, Google, etc.)
- Weekly GH Actions workflow opens issue when new models appear in
`workflow_templates` but not in `generated-models.json`
- `add-model-page` Claude skill for Slack-driven model page PRs

## Files changed

| File | Purpose |
|------|---------|
| `apps/website/src/config/generated-models.json` | Auto-generated, 207
models (27 partner + 180 local) |
| `apps/website/src/config/model-metadata.ts` | Editorial overrides:
docsUrl, blogUrl, featured (9 entries) |
| `apps/website/src/config/models.ts` | 65-line merger — imports JSON +
overrides, exports `models` + `getModelBySlug` |
| `apps/website/scripts/generate-models.ts` | Build-time parser; run
with `pnpm generate:models` |
| `apps/website/src/i18n/translations.ts` | ~30 UI keys added (no
per-model keys — displayName is plain string) |
| `apps/website/src/pages/p/supported-models/[slug].astro` | Dynamic
route with 3x JSON-LD schemas |
| `apps/website/src/pages/p/supported-models/index.astro` | Model grid
index page |
| `apps/website/src/components/models/ModelHeroSection.vue` | Hero
component |
| `.github/workflows/model-page-discovery.yaml` | Weekly auto-discovery
workflow |
| `.claude/skills/add-model-page/SKILL.md` | Claude skill for
adding/updating model pages |

## Test plan

- [ ] `pnpm build` passes in `apps/website`
- [ ] `/p/supported-models` index renders 207 model cards
- [ ] `/p/supported-models/kling-ai` shows Partner Node eyebrow, no
Download button, VIEW TUTORIAL CTA
- [ ] `/p/supported-models/flux-1-dev` shows Diffusion Model eyebrow,
Download + Tutorial buttons
- [ ] `/p/supported-models/umt5-xxl-fp8-e4m3fn-scaled` redirects 301 to
`umt5-xxl-fp16` (canonicalSlug)
- [ ] Structured data validator shows FAQPage + SoftwareApplication +
BreadcrumbList valid

Fixes FE-421

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 14:50:23 +00:00
..

Website Scripts

refresh-ashby-snapshot.ts

Pulls the latest job postings from Ashby and writes src/data/ashby-roles.snapshot.json. Invoked by the Release: Website GitHub Actions workflow; also runnable locally via pnpm --filter @comfyorg/website ashby:refresh-snapshot.

process-videos.sh

Generates multi-resolution VP9/WebM + H.264/MP4 variants and a poster frame for marketing videos using ffmpeg. Run locally before uploading the outputs to media.comfy.org; this is not wired into CI.

apps/website/scripts/process-videos.sh \
  ./video-sources \
  ./dist/videos \
  "640 960 1280 1920"

Output

For each source video at ./video-sources/foo.mp4, you get:

foo-640.webm   foo-640.mp4
foo-960.webm   foo-960.mp4
foo-1280.webm  foo-1280.mp4
foo-1920.webm  foo-1920.mp4
foo-poster.jpg

The naming convention is enforced by buildVideoSources() in src/utils/video.ts, which the <SiteVideo> Vue component uses to emit <source> URLs.

Pairing with <SiteVideo>

Once the assets are uploaded, render them with:

<SiteVideo
  name="foo"
  base-url="https://media.comfy.org/website/marketing"
  :width="1280"
  :formats="['webm', 'mp4']"
  poster="https://media.comfy.org/website/marketing/foo-poster.jpg"
  autoplay
  loop
/>

<SiteVideo> vs <VideoPlayer>

  • SiteVideo — lightweight multi-source <video> for decorative or autoplay marketing clips. No custom controls, no captions UI.
  • VideoPlayer — full-featured player with custom scrubber, mute, fullscreen, and caption toggles. Use this for content with subtitles or user-driven playback.

If you need both responsive sources and the rich VideoPlayer chrome, the two are not yet combined; either pick one or extend VideoPlayer to accept a source list.

Encoder choices

  • VP9/WebM at CRF 32 — preferred by Chrome and Firefox; smaller files.
  • H.264/MP4 at CRF 23, High profile, +faststart — universal fallback, required for Safari iOS.
  • Poster JPG at q4 — extracted from t=1s when the clip is long enough, otherwise t=0; scaled to 1280w. Use this as the poster attribute so the video shows something while loading.

Why a single resolution per video

<source media="..."> inside <video> is unreliable across browsers (Safari ignores it). The simplest correct strategy is to ship one well-sized resolution and let CSS scale it down on smaller viewports. The script generates multiple widths so you can pick a different one per page (e.g. 1280w for a hero, 640w for a thumbnail), or wire up JavaScript-based selection later if metrics demand it.