Spots Nuxt sites being shared on Bluesky and posts screenshots of them.
The aim of nuxt.fyi is to celebrate sites built with Nuxt in the wild.
It watches the Bluesky Jetstream firehose, pulls every link out of every post, and for each new domain asks: is this built with Nuxt? When the answer is yes, it posts a screenshot on Bluesky.
The public dashboard shows everything that’s been detected, broken down by version and recency.
<div id="__nuxt">, __NUXT_DATA__, window.__NUXT__, meta[name=generator], /_nuxt/ assets)I-Still-Dont-Care-About-Cookies extension)node:sqlite, global fetch and WebSocketYou will need a Discord webhook URL and, optionally, a Bluesky account with an app password for posting.
cp .env.example .env
# edit .env, set DISCORD_WEBHOOK_URL (and BLUESKY_* if you want)
corepack enable
pnpm install
pnpm install-extension
pnpm --filter @nuxt-fyi/scanner exec playwright install chromium
# scanner (terminal 1: screenshot service on :3001)
pnpm dev:scanner
# daemon (terminal 2)
pnpm dev
# dashboard (terminal 3)
pnpm dev:dashboard
Set VERBOSE=1 to log every post the daemon sees.
The dashboard’s home page has a small form for queueing a domain by hand. Under the
hood:
POST /api/submit rate-limits per IP (default 5 / minute, tunable viaSUBMIT_RATE_LIMIT and SUBMIT_RATE_WINDOW_MS).DAEMON_SUBMIT_PORT,DAEMON_SUBMIT_TOKEN bearer secret.RESCAN_AFTER_MS.Set DAEMON_SUBMIT_TOKEN as a Fly secret on nuxt-fyi to enable it in production; leave
it empty to disable submissions entirely (the daemon refuses every request, the dashboard
returns 503).
Screenshots are classified with nsfwjs on the
scanner machine at capture time. Each row gets one of three labels:
safe (default): renders normally everywhere.suggestive: renders normally on the dashboard; Bluesky posts get a sexual self-labelnsfw: dashboard blurs the image with a click-to-reveal overlay; Bluesky posts get aporn self-label; Discord posts attach the image as a SPOILER_* file rather thanThresholds are tunable via NSFW_PORN_THRESHOLD (default 0.5) and NSFW_SEXY_THRESHOLD
(default 0.6) on the scanner. After tweaking, re-run the backfill with --reclassify to
relabel historical rows.
Three processes split across two Fly apps:
nuxt-fyi (this fly.toml): daemon (Jetstream consumer + detection) and dashboard/data.nuxt-fyi-scanner (fly.scanner.toml): Nitro v3 service that owns Playwright + thenuxt-fyi-scanner.internal:3000 via Fly’s 6PN private networking. Stays always-on atshared-cpu-2x because .internal doesn’t wake stopped machines.The daemon talks to the scanner via an authenticated HTTP call (SCANNER_TOKEN shared
secret). Scanner outages degrade quality (no screenshot) but don’t break the pipeline:
the og:image is still uploaded and the hit is still recorded + posted.
Screenshots and og:images are uploaded to ImageKit at scan time
so the dashboard can render them through @nuxt/image with on-the-fly resizing. The
dashboard renders ImageKit URLs only; for the rare row where the daemon recorded an
og:image origin but the upload didn’t land, a plain <img> falls back to the upstream
URL.
Two idempotent scripts on the daemon side, safe to re-run:
# Rescan Nuxt-confirmed rows missing an ImageKit screenshot, or with an og:image URL
# recorded but no ImageKit copy. Uses the live scanner; ignores RESCAN_AFTER_MS.
pnpm backfill-images --concurrency=2
# NSFW-classify every row that has an image but no nsfw_label. Calls the scanner's
# /classify endpoint. Add --reclassify after threshold tweaks to relabel all rows.
pnpm backfill-nsfw --concurrency=2
Both support --limit=N and --dry-run.
On the running Fly machine (fly ssh console -C bash), you can re-scan one or more
domains against the live database, or check what we currently know about one:
cd /app
node src/cli/rescan.ts example.com another.com
# refresh only the screenshot/og:image on an existing Nuxt hit
node src/cli/rescan.ts --screenshot-only example.com
# suppress Discord + Bluesky posts even when the domain is newly detected as Nuxt
node src/cli/rescan.ts --no-notify example.com
# read-only: dump the stored scan, NSFW classification, activity, and notifications.
# Pass --json for machine-readable output.
node src/cli/status.ts example.com
Detection heuristics and the GTM consent-cookie trick are lifted from nuxtlabs/vue-telescope-analyzer ❤️
Made with ❤️
Published under MIT License.