Skip to content

seo(landing): query-driven titles, H1s and descriptions for the framework pages, home and top storage guides - #417

Open
BSalaeddin wants to merge 1 commit into
devfrom
seo/money-page-copy
Open

seo(landing): query-driven titles, H1s and descriptions for the framework pages, home and top storage guides#417
BSalaeddin wants to merge 1 commit into
devfrom
seo/money-page-copy

Conversation

@BSalaeddin

Copy link
Copy Markdown
Collaborator

Why

Search Console, 90 days to 2026-09-08 (sc-domain:useupup.com): 27 clicks on
5,812 impressions
. The impressions are already there; the titles and
descriptions that have to convert them are not.

Page Impr Pos Clicks Problem
/ (+ query upup) 1,253 (663) 9.8 (6.1) 19 (2) <title> 77 chars — truncated in SERPs; description 262 chars
/vue/ 486 47.6 0 title carried no qualifier, H1 said "One File Uploader, Vue"
/react/ 312 43 0 same
/angular/ 291 35.5 0 ~168 impr across angular-file-uploader / angular file uploader / file uploader angular
/svelte/ 87 31 0 same
/vanilla/ 104 19 3 same
/docs/guides/storage/azure-blob/ 321 32 ≤1 queries are upload files to blob storage, azure upload blob with sas url put blob headers — the title said none of it
/docs/guides/storage/digitalocean-spaces/ 210 29 ≤1 same shape
/docs/comparisons/upup-vs-uploadthing/ 141 33 ≤1 uploadthing 10 impr @38
/docs/guides/storage/cloudflare-r2/ 115 41 ≤1
/docs/guides/server-mode-setup/ 113 13.5 ≤1
/docs/guides/storage/minio/ 74 20 ≤1
/docs/resumable-uploads/ 70 14.7 ≤1
/docs/comparisons/upup-vs-uppy/ 65 12.6 ≤1
/docs/api-reference/events/ 51 7.7 ≤1 page one, title was the single word "Events"
/docs/ 155 13.6 ≤1

A docs crawl also flagged 7 descriptions over 170 chars; the real census found
24 of 64 docs pages with a description Google would cut off.

vue file manager (58 impr @65) was deliberately not targeted — upup is not
a file manager, so chasing it would be a false claim.

What changed

Titles are ≤ 60 chars with the brand last on non-home pages and first on
home; descriptions are 120–155 and carry the phrasing searchers actually type
(file uploader, drag and drop, free, open source, npm install @useupup/<fw>, resumable, S3). No invented claims — no user counts, no
"most popular", no "#1"; "free and open-source (MIT)" and "six frameworks" are
both true. byte-identical is not reintroduced anywhere (a separate PR retires
that claim).

Home

Before After
<title> upup – One File Uploader for React, Vue, Svelte, Angular, Vanilla JS & Preact (77) upup – Open-Source File Uploader for Every Framework (52)
meta description One open-source, MIT-licensed file uploader with a headless core and byte-identical native UI for six frameworks. Drag-and-drop, cloud drives (Google Drive, OneDrive, Dropbox, Box), camera, screen capture, and secure server-mode uploads to S3-compatible storage. (262) upup is a free, open-source drag and drop file uploader with native UI for React, Vue, Svelte, Angular, Vanilla JS and Preact. MIT-licensed. (140)
<h1> One File Uploader, Every Framework Open-Source File Uploader for Every Framework (6 words)
siteConfig.title (site-wide fallback) upup – One File Uploader for Every Framework | Open Source (58) upup – Open-Source File Uploader for Every Framework (52)
siteConfig.tagline (site-wide fallback) 216 chars Free and open-source (MIT) drag and drop file uploader for React, Vue, Svelte, Angular, Vanilla JS and Preact, with resumable uploads to S3. (140)

Framework pages

<title> template went from <Name> File Uploader — upup to
<Name> File Uploader – Open-Source Drag & Drop | upup; the description is now
the per-framework tagline in src/lib/frameworks.tsx (it had no other
consumer). The H1's two-line structure is unchanged — the BlurText line now
carries <Name> File Uploader and the gradient line carries
One core, every framework.

Page <title> (len) <h1> description (len)
/react/ React File Uploader – Open-Source Drag & Drop | upup (52) React File Uploader / One core, every framework Free, open-source React file uploader with drag and drop, cloud drives, an image editor and resumable S3 uploads — npm install @useupup/react. (142)
/vue/ Vue File Uploader – Open-Source Drag & Drop | upup (50) Vue File Uploader / … Free, open-source Vue file uploader with drag and drop, cloud drives and resumable uploads to any S3 storage — npm install @useupup/vue. (136)
/svelte/ Svelte File Uploader – Open-Source Drag & Drop | upup (53) Svelte File Uploader / … Free, open-source Svelte file uploader with drag and drop, cloud drives and resumable uploads to any S3 storage — npm install @useupup/svelte. (142)
/angular/ Angular File Uploader – Open-Source Drag & Drop | upup (54) Angular File Uploader / … Free, open-source Angular file uploader with drag and drop, cloud drives and resumable uploads to any S3 storage — npm install @useupup/angular. (144)
/vanilla/ Vanilla JS File Uploader – Open-Source Drag & Drop | upup (57) Vanilla JS File Uploader / … Free, open-source file uploader for plain JavaScript and TypeScript — drag and drop, cloud drives, resumable S3 uploads. npm install @useupup/vanilla. (150)
/preact/ Preact File Uploader – Open-Source Drag & Drop | upup (53) Preact File Uploader / … Free, open-source Preact file uploader with drag and drop, cloud drives, an image editor and resumable S3 uploads — npm install @useupup/preact. (144)

Each framework page also gained a real internal link to its own quickstart
(/docs/quickstarts/<id>/, trailing slash) in the hero CTA row — all six slugs
exist under content/docs/quickstarts/. It renders only when fw is set, so
the home page is unchanged. It is a plain <Link> with no motion wrapper,
deliberately, so it does not collide with seo/mobile-performance, which
rewrites the hero's animation code in the same file. The rest of my hero edit is
three text-only hunks.

Docs frontmatter

Every listed page was retitled to the query language, plus the 24 descriptions
that were over 160 chars (the budget test would otherwise fail on them, and the
brief says fix rather than loosen).

Lengths below are title chars (add 12 for the | upup docs template) →
description chars.

Page title before → after title len desc len
guides/storage/azure-blob Upload files to Azure Blob StorageUpload Files to Azure Blob Storage with a SAS URL 34 → 49 158 → 159
guides/storage/digitalocean-spaces Upload files to DigitalOcean SpacesUpload Files to DigitalOcean Spaces from the Browser 35 → 52 158 → 156
guides/storage/cloudflare-r2 Upload files to Cloudflare R2Upload Files to Cloudflare R2 from the Browser 29 → 46 159 → 149
guides/storage/minio Upload files to MinIOUpload Files to MinIO from the Browser 21 → 38 159 → 153
guides/server-mode-setup Server Mode — SetupServer Mode Setup — Upload Through Your Server 19 → 46 152 → 155
resumable-uploads Resumable UploadsResumable Uploads — S3 Multipart & tus 17 → 38 195 → 152
comparisons/upup-vs-uploadthing upup vs UploadThingupup vs UploadThing — Self-Hosted Alternative 19 → 45 157 → 154
comparisons/upup-vs-uppy upup vs Uppyupup vs Uppy — Which Uploader to Choose 12 → 39 159 → 153
api-reference/events EventsFile Upload Events 6 → 18 151 (unchanged)
index (/docs/) upup DocumentationFile Uploader Documentation 18 → 27 151 → 154
7 × quickstarts/* unchanged 163–184 → 149–155, each now leads with Add a file uploader to a <fw> app with @useupup/<fw>
14 other pages unchanged 161–224 → ≤ 160, trimmed without adding claims

The azure-blob description now carries the exact query shape:
a server-issued SAS URL, the mandatory x-ms-blob-type PUT header, and
"no account key in the client".

Colons were kept out of the new titles: an unquoted YAML scalar cannot contain
": ", and the corpus's existing style is the em dash. All 64 pages still parse
and the page count is still 64 (docs-source.test.ts / docs-llms.test.ts
pins untouched).

What CI covers

New apps/landing/src/__tests__/seo-copy-budgets.test.ts — the budgets stop
being a convention and start being a gate:

  • home <title> ≤ 60, starts with the brand; description 120–160
  • siteConfig.title / .tagline (the site-wide fallback metadata) under the
    same budgets
  • every framework page, via a real generateMetadata(FRAMEWORK_ID) call:
    title ≤ 60, title contains <Name> File Uploader, description 120–160,
    description contains npm install <pkg>
  • every docs page from source.getPages(): frontmatter title + the
    " | upup docs" template ≤ 65; description ≤ 160 where present — both report
    the offending URLs and their lengths rather than a bare boolean
  • no title or description anywhere reintroduces byte-identical

Verification

All run locally from the worktree, verdicts via rtk proxy + $LASTEXITCODE
(the rtk filter has falsified prettier/tsc/knip verdicts before).

Gate Result
turbo run lint typecheck test --filter=@useupup/landing --force @useupup/landing lint ✅, typecheck ✅; the new seo-copy-budgets.test.ts 31 tests passed in 49 ms; docs-source ✅ (still 64 pages), docs-llms ✅, core 146 ✅, react 74 ✅, server 32 ✅, interactive-example 18 ✅. See the flake note below.
pnpm run test:quality ✅ 400 test files + 5 workflows clean (new file staged first — the guard discovers via git ls-files)
pnpm run vocab:check ✅ 1410 tracked files clean of 15 retired tokens
pnpm run docs:links:check ✅ 403 links, 105 anchors, 8 redirect targets across 64 pages
pnpm run docs:api-sync:check ✅ 258 public API members
pnpm run docs:snippets:coverage ✅ 1 topic × 7 frameworks
prettier --check on every touched .ts/.tsx ✅ (.mdx is not in lint-staged's globs or the root prettier scope)
turbo run build --filter=@useupup/landing --force ✅ 4/4 tasks, 148/148 static pages generated
Playwright --project docs (playwright.landing.config.ts) 15 passed (3.3 m) — including docs page renders chrome and content (asserts the /docs/getting-started/ <h1>, whose title I deliberately left alone), copy-page markdown twin (# Getting Started), and docs home renders hero…

Pre-existing flake, not from this PR: apps/landing/tests/support-route.test.ts
fails its first test (silently accepts a honeypot-filled submission…) on a
5 s timeout — that test does the cold loadRoute() dynamic import and the
transform alone took 30 s on this (heavily loaded) Windows box; the other 13
pass. Its import graph (@/lib/analytics/*, @/lib/support/*) has zero
overlap with anything this PR touches. packages/react and
tests/analytics-isolation.test.ts failed the same way on the first run and
went green on the isolated re-run, per CLAUDE.md's flake protocol.

Served HTML (production build, next start -p 4468)

### /  (HTTP 200)
<title>      upup – Open-Source File Uploader for Every Framework   [52]
description  upup is a free, open-source drag and drop file uploader with native UI for React, Vue, Svelte, Angular, Vanilla JS and Preact. MIT-licensed.   [140]
<h1>         Open-Source File Uploader / for Every Framework

### /vue/  (HTTP 200)
<title>      Vue File Uploader – Open-Source Drag &amp; Drop | upup   [50]
description  Free, open-source Vue file uploader with drag and drop, cloud drives and resumable uploads to any S3 storage — npm install @useupup/vue.   [136]
<h1>         Vue File Uploader / One core, every framework

### /docs/guides/storage/azure-blob/  (HTTP 200)
<title>      Upload Files to Azure Blob Storage with a SAS URL | upup docs   [61]
description  Upload files to Azure Blob Storage from the browser with upup — a server-issued SAS URL, the mandatory x-ms-blob-type PUT header, no account key in the client.   [159]
<h1>         Upload Files to Azure Blob Storage with a SAS URL

All six framework pages, from the same server (title length after entity
decoding, and the quickstart link asserted in the HTML):

/react/     title[52] React File Uploader – Open-Source Drag & Drop | upup       quickstart link: true
/vue/       title[50] Vue File Uploader – Open-Source Drag & Drop | upup         quickstart link: true
/svelte/    title[53] Svelte File Uploader – Open-Source Drag & Drop | upup      quickstart link: true
/angular/   title[54] Angular File Uploader – Open-Source Drag & Drop | upup     quickstart link: true
/vanilla/   title[57] Vanilla JS File Uploader – Open-Source Drag & Drop | upup  quickstart link: true
/preact/    title[53] Preact File Uploader – Open-Source Drag & Drop | upup      quickstart link: true

Owner questions

  1. Framework-page H1 second line. I kept the two-line hero and put
    One core, every framework in the gradient. Would you rather it read
    Open source, MIT licensed or the framework's own package name?
  2. /docs/ title. File Uploader Documentation | upup docs reads slightly
    redundant but puts the head term in front of the brand; upup Documentation
    did not rank. Happy to revert if you prefer the brand-first form.
  3. api-reference/events sidebar label changed from Events to
    File Upload Events — frontmatter title drives the <title>, the on-page
    <h1> and the sidebar entry, so every retitle in this PR also changes those.
    Same applies to the storage guides and the two comparisons.
  4. Not done, deliberately: FrameworkStrip links to /${fw.id} with no
    trailing slash
    , so every internal framework link eats a 308 under
    trailingSlash: true. One-character fix, but that file is outside this PR's
    brief and seo/mobile-performance is in the same neighbourhood — say the
    word and I'll take it, or leave it for the hygiene PR.
  5. FAQ. I did not append framework-specific Q&As to src/lib/faqs.ts.
    The FAQ block is shared by the home page and all six framework pages, so
    per-framework entries would show on every page, and seo/claim-integrity
    owns that file right now (it removes the byte-identical answer). Say the
    word and I will add them in a follow-up.

@codesandbox

codesandbox Bot commented Sep 12, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant