Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions .github/workflows/seo-articles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: SEO Articles

on:
schedule:
# Every Monday at 08:00 UTC
- cron: '0 8 * * 1'
workflow_dispatch:

jobs:
generate:
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
token: ${{ secrets.PAT }}

- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0

- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
cache: pnpm

- name: Install dependencies
run: pnpm install --filter @semianalysisai/inferencex-app...
env:
CYPRESS_INSTALL_BINARY: '0'

- name: Fetch benchmark data
run: pnpm admin:seo:data

- name: Generate articles with Claude
uses: anthropics/claude-code-action@88c168b39e7e64da0286d812b6e9fbebb6708185 # v1.0.82
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.PAT }}
direct_prompt: |
You are generating SEO blog articles for InferenceX from benchmark data.

## Instructions
1. Read the article template at `packages/app/scripts/seo/article-template.md`
2. Read the benchmark data at `packages/app/tmp/seo-data.json`
3. For each model in the data, create or update an MDX file at
`packages/app/content/blog/best-gpu-for-<modelKey>-inference.mdx`
4. Create/update the rollup article at
`packages/app/content/blog/inference-benchmark-roundup.mdx`

Follow the template structure EXACTLY — same sections, same order, same table format.
Write natural, varied prose for each model (not copy-paste between articles).

## Critical Rules
- ALL numbers MUST come from the data file. Never invent or estimate numbers.
- TTFT/TPOT/E2EL values in the data are in SECONDS — multiply by 1000 for display in ms.
- Use gpuDisplayName from the data (e.g. "NVIDIA B200", "AMD MI 355X").
- Preserve the `date` frontmatter from existing files (check if file exists first).
- The runner-up in Key Findings must be a DIFFERENT GPU than the winner.
- Skip the 1k/8k sequence entirely — it is deprecated.
- Format large numbers with commas (e.g. 18,131.6).
allowed_tools: 'Read,Write,Edit,Glob,Grep,Bash'
claude_args: '--model claude-sonnet-4-5-20250929'

- name: Check for changes
id: changes
run: |
if git diff --quiet -- 'packages/app/content/blog/'; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Create PR
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.PAT }}
run: |
DATE=$(date +%Y-%m-%d)
BRANCH="seo/update-articles-${DATE}"
git checkout -b "$BRANCH"
git add 'packages/app/content/blog/'
git commit -m "update SEO benchmark articles (${DATE})"
git push origin "$BRANCH"
gh pr create \
--title "update SEO benchmark articles (${DATE})" \
--body "$(cat <<'EOF'
## Summary
- Auto-generated SEO blog articles from latest benchmark data
- Articles written by Claude using real data from `seo-data.json`

## Review checklist
- [ ] Spot-check numbers against live dashboard
- [ ] Verify JSON-LD in page source
- [ ] Read prose for quality and accuracy
EOF
)" \
--base master
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
**/inferencex-backup-*.dump

# local data
**/tmp
**/gcs
**/logs
**/public/data/*
Expand Down
31 changes: 30 additions & 1 deletion docs/blog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ title: string
subtitle: string
date: YYYY-MM-DD
modifiedDate?: YYYY-MM-DD # Used in sitemap and JSON-LD
publishDate?: YYYY-MM-DD # Scheduled publishing, hidden in production until this date
tags?: string[] # Used for filtering on /blog and in RSS categories
```

### Scheduled Publishing (`publishDate`)

Posts without `publishDate` are hidden in production — this field is required for a post to be visible. If `publishDate` is set to a future date, the post is hidden until that date arrives. In development, all posts are visible regardless. This allows articles to be merged to `master` via PR and go live automatically when the date arrives. All downstream consumers (sitemap, RSS, llms.txt) automatically respect the filter since they call `getAllPosts()`. `getPostBySlug()` still returns the post regardless of `publishDate` (for direct URL preview).

Slug is derived from the filename (e.g., `my-post.mdx` -> `my-post`), not from frontmatter. Reading time is calculated at 265 WPM.

## MDX Components Available to Authors
Expand All @@ -32,6 +37,7 @@ Slug is derived from the filename (e.g., `my-post.mdx` -> `my-post`), not from f
| `![alt](src)` | Images | Rendered via `next/image` with lazy loading (first image is eager) |
| `<Figure src="..." alt="..." caption="..." />` | Captioned figures | Uses `<img>` (not `next/image`) for external URLs |
| `<Blur>...</Blur>` | Paywall teaser blur overlay | Content is blurred, unselectable, and not clickable |
| `<JsonLd>{...}</JsonLd>` | Structured data (JSON-LD) | Renders `<script type="application/ld+json">`. Validates JSON before rendering. |

Heading ID deduplication: if two headings share a slug, the second gets prefixed with its parent heading's slug (e.g., `overview-details`). If no parent exists, a level suffix is appended (`intro-2`).

Expand Down Expand Up @@ -111,7 +117,30 @@ All blog analytics use the `blog_` prefix per the `[section]_[action]` conventio
## Adding a New Blog Post

1. Create `packages/app/content/blog/<slug>.mdx` with required frontmatter (`title`, `subtitle`, `date`)
2. Add optional `tags` and `modifiedDate` frontmatter
2. Add optional `tags`, `modifiedDate`, and `publishDate` frontmatter
3. Write content using standard Markdown + available MDX components
4. The post automatically appears in: blog list, sitemap, RSS feed, llms.txt, OG image generation
5. No code changes needed — just the MDX file

## Auto-Generated SEO Articles

A weekly GitHub Action (`.github/workflows/seo-articles.yml`) generates per-model benchmark articles from InferenceX data:

1. **Data script** (`packages/app/scripts/generate-seo-articles.ts`) fetches benchmark data from the production API and writes a JSON summary to `packages/app/tmp/seo-data.json`
2. **Claude Code Action** reads the data + article template (`packages/app/scripts/seo/article-template.md`) and writes MDX files to `content/blog/`
3. A PR is created for human review before merge

### Running locally

```bash
pnpm admin:seo:data # fetch data from production
pnpm admin:seo:data --base-url http://localhost:3000 # fetch from local dev
```

### Article template

The template at `packages/app/scripts/seo/article-template.md` defines the section order and formatting rules. Claude adapts depth and tone to each model's data richness — sparse-data models get shorter, honest articles; data-rich models get detailed analysis with framework and disagg comparisons.

### Slugs

Per-model articles use `best-gpu-for-<modelKey>-inference` slugs (e.g., `best-gpu-for-dsr1-inference`). The rollup uses `inference-benchmark-roundup`. Slugs are stable across updates to preserve SEO authority.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"admin:db:migrate": "pnpm --filter *db db:migrate",
"admin:db:apply-overrides": "pnpm --filter *db db:apply-overrides",
"admin:db:reset": "pnpm --filter *db db:reset",
"admin:db:verify": "pnpm --filter *db db:verify"
"admin:db:verify": "pnpm --filter *db db:verify",
"admin:seo:data": "pnpm --filter *app seo:data"
},
"devDependencies": {
"audit-ci": "^7.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: 'InferenceMAX: Open Source Inference Benchmarking'
subtitle: 'NVIDIA GB200 NVL72, AMD MI355X, Throughput Token per GPU, Latency Tok/s/user, Perf per Dollar, Cost per Million Tokens, Tokens per Provisioned Megawatt, DeepSeek R1 670B, GPTOSS 120B, Llama3 70B'
date: '2025-10-09'
publishDate: '2025-10-09'
tags:
- benchmark
- gpu
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: 'InferenceX v2: NVIDIA Blackwell Vs AMD vs Hopper - Formerly InferenceMAX'
subtitle: 'GB300 NVL72, MI355X, B200, H100, Disaggregated Serving, Wide Expert Parallelism, Large Mixture of Experts, SGLang, vLLM, TRTLLM'
date: '2026-02-16'
publishDate: '2026-02-16'
tags:
- benchmark
- gpu
Expand Down
3 changes: 2 additions & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"clean": "rimraf .next out",
"clean:all": "rimraf .next out cypress/videos cypress/screenshots coverage",
"cache:invalidate": "dotenv -e ../../.env -- tsx scripts/invalidate-cache.ts",
"cache:warmup": "dotenv -e ../../.env -- tsx scripts/warmup-cache.ts"
"cache:warmup": "dotenv -e ../../.env -- tsx scripts/warmup-cache.ts",
"seo:data": "tsx scripts/generate-seo-articles.ts"
},
"dependencies": {
"@jpinsonneau/html-to-image": "^1.11.13",
Expand Down
Loading
Loading