Skip to content

seo(landing): serve the IndexNow key file - #416

Open
BSalaeddin wants to merge 1 commit into
devfrom
seo/indexnow-key
Open

seo(landing): serve the IndexNow key file#416
BSalaeddin wants to merge 1 commit into
devfrom
seo/indexnow-key

Conversation

@BSalaeddin

Copy link
Copy Markdown
Collaborator

What

Serves the IndexNow ownership key file on the indexable host of useupup.com
(apps/landing):

  • apps/landing/src/lib/indexnow.ts — one constant module holding
    INDEXNOW_KEY / INDEXNOW_KEY_PATH, with the protocol rules that make the
    file load-bearing written down next to it.
  • apps/landing/src/app/5cb30cbda540958e8d033652400e59e3.txt/route.ts — a
    force-static route handler returning the bare key as
    text/plain; charset=utf-8, cached a day. (A directory whose name ends in
    .txt is an established route shape in this app — src/app/docs-llms/llms.txt/
    already does it.)
  • apps/landing/src/__tests__/indexnow.test.ts — pins the directory name and
    the constant to each other.

No dependencies added, no config changed, nothing submitted anywhere.

Why

IndexNow lets us push URL changes to Bing / Yandex / Seznam / Naver the moment
they happen instead of waiting to be recrawled. Bing currently reports 56
pages indexed and 0 clicks
for this domain (2026-09-11 fleet pull), so
getting new and changed docs pages in front of it promptly is cheap upside.

The protocol proves ownership by fetching https://<host>/<key>.txt and
requiring the body to be exactly the key
(https://www.indexnow.org/documentation). The key file has to be live in
production before a single submission can succeed
— that is the whole scope
of this PR.

The key is freshly generated for this app and is not a secret: publishing it is
the mechanism.

What CI covers

apps/landing/src/__tests__/indexnow.test.ts guards the one coupling that can
silently break verification — the App Router directory is named after the key,
so a change to the constant without the matching rename (or vice versa) leaves
the site serving one key while claiming another, and every submission fails
ownership against a 200 response. It asserts:

  • the key is 32 lowercase hex characters (IndexNow's format rule);
  • INDEXNOW_KEY_PATH is the root-level .txt form of it;
  • src/app/<INDEXNOW_KEY>.txt/route.ts exists on disk, resolved from the
    constant — not from a second hardcoded copy of the key;
  • GET() returns 200, content-type: text/plain; charset=utf-8, and a body
    that is exactly the key with nothing appended.

Indexability was checked rather than assumed: src/app/robots.ts disallows only
/api/ and /mobile-demo/, and src/app/sitemap.ts enumerates explicit routes,
so the key file is neither blocked nor advertised as content.

Verification

Gates, from the worktree (via rtk proxy wherever output fidelity matters):

Gate Result
turbo run lint typecheck test --filter=@useupup/landing --force landing lint 0, typecheck 0, tests 71 passed / 9 files (incl. the 4 new)
pnpm run test:quality OK — 400 test files + 5 workflows clean, 0 exceptions pinned
pnpm run vocab:check OK — 1413 tracked files clean of 15 retired tokens
prettier --check on the three touched files All matched files use Prettier code style
turbo run build --filter=@useupup/landing --force 4 successful / 4 total, exit 0

The build prerenders the key file statically —
.next/server/app/5cb30cbda540958e8d033652400e59e3.txt.body is 32 bytes,
exactly the key with no trailing newline.

Noted for the record, not a finding: @useupup/core
tests/strategies/tus-upload-missing.test.ts intermittently timed out at 15 s
while the authoring box was heavily loaded (vitest reported transform 858s / import 1110s under contention from other checkouts). Per the flake protocol it
was re-run isolated — @useupup/core is 146 files / 1843 tests, all green,
exit 0
— and the pre-commit hook then passed clean. Nothing in this diff
touches packages/.

Live against a production next build + next start -p 4467:

$ curl -s -i http://localhost:4467/5cb30cbda540958e8d033652400e59e3.txt
HTTP/1.1 200 OK
vary: rsc, next-router-state-tree, next-router-prefetch, next-router-segment-prefetch
x-nextjs-cache: HIT
cache-control: public, max-age=86400
content-type: text/plain; charset=utf-8
Date: Sat, 12 Sep 2026 02:05:58 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

5cb30cbda540958e8d033652400e59e3

trailingSlash: true adds no redirect here, which is the property IndexNow
actually depends on — an engine that gets a 308 on the key file treats
verification as failed. Confirmed rather than assumed:

$ curl -s -o /dev/null -w "http_code=%{http_code} num_redirects=%{num_redirects}\n" -L \
    http://localhost:4467/5cb30cbda540958e8d033652400e59e3.txt
http_code=200 num_redirects=0

That matches the rule already documented in next.config.mjs: Next never
appends a trailing slash to a path with an extension. The body was also compared
byte-for-byte against the constant (32 chars, exact match, no newline).

Crawl policy checked on the same running server: /robots.txt served
Disallow: /api/ + Disallow: /mobile-demo/ only, and /sitemap.xml does not
contain the key.

Next step (not in this PR)

Owner / SEO: once this is deployed to production and the URL returns 200 there,
submit URLs through the fleet SEO MCP's IndexNow surface. No submission API is
called from this codebase, and none should be added here — the app's job ends at
serving the key.

@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