cloudflare / cloudflare/vinext
Streaming metadata/PPR: match HTML-limited bots as substrings of full user-agent (anchored bypass rule)
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
## Next.js Change
**Commit:** [`d87a203`](https://github.com/vercel/next.js/commit/d87a203bccb292e7717c4b956069f73150e44eca)
**PR:** [#96584](https://github.com/vercel/next.js/pull/96584)
## What changed
Fixes HTML-limited bot detection for the PPR / streaming-metadata **cache-bypass** rule. When Cache Components / PPR is enabled, Next.js generates a route `has` matcher on the `user-agent` header that selectively bypasses the static prerender shell (and blocks streamed metadata) for HTML-limited bots.
The generated matcher value was the raw bot-substring pattern (either `config.htmlLimitedBots` or the built-in `HTML_LIMITED_BOT_UA_RE_STRING`). But route `has` matchers **anchor** header values (`^...$`), so a full user-agent string that merely *contains* a bot token (e.g. `googleweblight` embedded in a real Chrome/Android UA, or a custom `MyBot` inside `Mozilla/5.0 (compatible; MyBot; ...)`) would fail to match and would incorrectly be served the streamed/PPR shell instead of the bot-appropriate blocking response.
The fix wraps the bot pattern in surrounding wildcards so anchored matching preserves `RegExp.test()` substring semantics:
```ts
// packages/next/src/build/index.ts
const htmlLimitedBotsRegexString =
config.htmlLimitedBots || HTML_LIMITED_BOT_UA_RE_STRING
// Route `has` matchers anchor header values. Add surrounding
// wildcards to preserve RegExp.test() substring semantics for
// complete user-agent strings.
const htmlLimitedBotsBypassRegexString = `.*(?:${htmlLimitedBotsRegexString}).*`
// ...used as the `has` header matcher value:
{
type: 'header',
key: 'user-agent',
value: htmlLimitedBotsBypassRegexString, // was: htmlLimitedBotsRegexString
}
```
So the generated matcher becomes `.*(?:...bot patterns...).*` instead of the bare alternation.
## Impact on vinext
vinext reimplements PPR / streaming-metadata bot detection. Any place vinext decides whether to block/stream metadata (or bypass the prerender shell) based on the `user-agent` against the HTML-limited bots list must use **substring** matching against the full incoming user-agent, not an anchored/exact match of the bot-token pattern alone.
What to check/do:
1. **Match full user-agent strings.** When testing the incoming `user-agent` against `htmlLimitedBots` (or the default HTML-limited bot pattern), ensure bots embedded anywhere in the UA string match — e.g. `googleweblight` inside a full mobile UA, or a configured `MyBot` inside `Mozilla/5.0 (compatible; MyBot; +https://example.com/bot)`.
2. **If vinext emits an anchored `has`-style matcher** for this bypass rule (mirroring Next.js's routes-manifest generation), wrap the pattern as `.*(?:).*` so anchored matching still behaves as a substring test.
3. **Honor the `htmlLimitedBots` config option** the same way — the wildcard wrapping applies to both the custom pattern and the built-in default.
4. **Port the tests.** Next.js added coverage:
- full-UA `googleweblight` blocks metadata (default bot list)
- configured custom bot within a full UA blocks metadata
- routes-manifest snapshot asserting the matcher value is `.*(?:).*`, and that the anchored matcher matches a full googleweblight UA but not a plain `Mozilla/5.0`.
## Notes
- Relevant only once vinext implements streaming metadata / PPR bot handling (the `htmlLimitedBots` cache-bypass path). This is a matching-semantics correctness fix, not a new feature.
- The bug was silent: bots with tokens embedded in a longer UA (the common real-world case) were not detected, so they'd receive the streamed shell instead of the blocking metadata response.
Contributor guide
Research direction
Start by locating vinext's streaming-metadata/PPR bot-handling entry point and its htmlLimitedBots matching, then compare the intended behavior with packages/next/src/build/index.ts. Add coverage for default and configured bots embedded in full user-agent strings, plus the routes-manifest matcher behavior described in the issue. Done means embedded bot tokens match while a plain Mozilla/5.0 user-agent does not.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, typescript
- Domain
- backend, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100