Point campaigns SDK redirects at real sections, not the site root - #54
Conversation
All 43 rules moving /docs/campaigns/* to cart-sdk.nextcommerce.com pointed at https://cart-sdk.nextcommerce.com/latest/. Search Console flagged 38 URLs as "Blocked by robots.txt" on 2026-09-07: Google follows the redirect, finds cart-sdk's Disallow-all robots.txt, and attributes the block to the source URL. Unblocking cart-sdk (its DOCS_BASE_URL is being set separately) fixes the robots half but not this half — 35 distinct pages collapsing onto one destination is a soft-404 pattern, so the URLs would move from "Blocked" to "not indexed" rather than recover. Each rule now targets the section that absorbed the old page. The SDK site consolidated ~160 narrative pages into six reference pages plus the page-type guides, so the mapping is many-to-one; every destination page and every anchor was verified against the built site. Two deliberate gaps: - FOMO gets no rule, and the /docs/campaigns/utilities/* catch-all is dropped so it cannot swallow those paths. The feature is gone from the SDK (no matches in src/), so a 404 is the honest answer. - /docs/campaigns/campaign-cart/cart-system is unlisted; the generic campaign-cart/:path rule already rewrites it into /docs/campaigns/, which these rules then resolve. netlify.toml and public/_redirects are generated from one table and verified identical. public/_redirects is what Cloudflare actually serves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
developer-docs | 38be9f9 | Commit Preview URL Branch Preview URL |
Sep 07 2026, 09:24 AM |
Code Review SummaryStatus: No Issues Found | Recommendation: Merge The previous SUGGESTION about ambiguous Files Reviewed (2 files)
Previous Review Summaries (6 snapshots, latest commit 98da688)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 98da688)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (2 files)
Fix these issues in Kilo Cloud Previous review (commit ebe8716)Status: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (2 files)
Fix these issues in Kilo Cloud Previous review (commit d6d2a48)Status: No Issues Found | Recommendation: Merge The new commit ( All 14 prior inline comments are stale on the current diff: every comment referenced a now-deleted enumerated exact rule whose specific anchor ( No new changed-code issues. The same-destination exact+splat pairs eliminate the "which match wins" question the previous review asked, and the new section-level splats are intentional behavior, not a regression. Files Reviewed (2 files)
Previous review (commit 123caba)Status: No Issues Found | Recommendation: Merge The new commit ( All 14 existing inline comments remain active and accurate against the current HEAD; none were resolved or rendered stale by this retrigger. No new findings. Files Reviewed (2 files)
Previous review (commit 02d721c)Status: No Issues Found | Recommendation: Merge The new commit (
Re-verified the full current diff ( Files Reviewed (2 files)
Previous review (commit 6de16d3)Status: 7 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
The same gaps exist row-for-row in The remaining changes (no-shadow ordering, deliberate Files Reviewed (2 files)
Reviewed by minimax-m3 · Input: 23.3K · Output: 1.2K · Cached: 198.9K |
Verified the previous commit against the Cloudflare preview deploy and found the rules were not resolving as written. /docs/campaigns/checkout/payment went to the generic checkout page rather than the payment section, /docs/campaigns/data-attributes/state lost its anchor, and bundle-selector/vouchers landed on #bundle-selector instead of #voucher-codes. In every case the parent wildcard beat a more specific rule listed above it, so Cloudflare is not applying first-match-wins the way Netlify documents it. Rather than reverse-engineer the precedence and burn deploy cycles on it, this removes the ambiguity: every rule is now an exact path, one per page that actually existed, with no wildcards in the block. No two rules can match the same URL, so precedence stops mattering on either host. The path list is generated from the pages deleted in this repo's own history, plus three URLs Search Console has indexed that predate it — the rudderstack and google-tag-manager analytics examples, and campaign-cart/cart-system. Enumeration from git alone would have missed those, which is worth remembering if more turn up. Verified against the built SDK site: 10 destination pages all 200, 32 anchors all present as real element ids, both files identical across 138 rules, no duplicate `from`, no still-live page (api/, page-kit, templates, admin-api) caught by a rule. Replaying the Search Console export gives 33 of 35 mapped, the other two being the FOMO paths that are meant to 404. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The build for 02d721c failed with no log text exposed on the check run. The change is config-only, `npm run build` succeeds locally, `out/_redirects` is byte-identical to `public/_redirects`, every line is well-formed, and the file is far inside Cloudflare's documented 2,000 static / 100 dynamic limits at 167 and 4. Retriggering to separate a transient deploy failure from a real one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The deploy for 02d721c was rejected by the Cloudflare API: Invalid _redirects configuration: Line 121: Maximum number of dynamic _redirects rules limit of 100 exceeded Cloudflare counts every cross-host redirect as dynamic, not just those with a splat or placeholder. The earlier "167 static / 4 dynamic" reading used the wrong definition. Measured properly: 6de16d3 79 external + 4 splat = 83 deployed 02d721c 138 external + 4 splat = 142 rejected Enumerating one rule per historical page bought precision at the cost of a hard platform limit. This replaces it with one destination per section: 29 rules, 33 dynamic in total, comfortably inside the cap. The section shape also removes the precedence problem that motivated the enumeration. Each section's exact rule and its splat point at the same target, so it no longer matters which one Cloudflare matches first — the ordering question that produced the wrong destinations on the earlier preview simply cannot arise. The cost is anchor granularity: /checkout/payment now lands on the checkout page rather than #payment-methods. What matters for indexing is that every URL reaches the page that absorbed its content instead of the site root, which Google reads as a soft 404. Section anchors are kept where a whole section maps to one. FOMO still has no rule and there is deliberately no /utilities/* catch-all to swallow it, so its five paths 404. The three surviving utilities are listed individually. Verified: both files in sync across all 29 rules, no duplicate `from`, no rule catching a still-live page, no section with conflicting destinations, all 134 historical paths covered, and 32 of 35 flagged Search Console URLs mapped — the rest being the two FOMO paths and campaign-cart/cart-system, which the existing campaign-cart/:path rule already handles. `npm run check` passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A full sweep of all 140 historical paths against the preview deploy found /docs/campaigns/campaign-cart returning 404. The rule was left out on the assumption that the existing campaign-cart/:path rule covered it, but :path only matches sub-paths — campaign-cart/cart-system resolves correctly while the section index itself falls through to a 404. It was a real published page (campaign-cart/index.mdx), so it needs its own rule. Takes the block to 30 rules, 34 dynamic against Cloudflare's cap of 100. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit added an exact rule for the bare campaign-cart index but left the block comment above claiming campaign-cart is handled entirely by the campaign-cart/:path rule. Sub-paths are; the index has its own rule. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two rules above end in campaign-cart/:path — /docs/campaign-cart/:path and /docs/campaigns/campaign-cart/:path. Both comments referring to "the campaign-cart/:path rule above" now name the full pattern, in each file. Comments only; no rule changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why
Search Console flagged 38 URLs as Blocked by robots.txt on 2026-09-07. 35 were
developers.nextcommerce.com/docs/campaigns/*pages redirecting tohttps://cart-sdk.nextcommerce.com/latest/, and cart-sdk servedDisallow: /. Google follows the redirect, hits the disallowed host, and attributes the block to the source URL.The root cause is fixed separately:
campaign-cart'sDOCS_BASE_URLrepo variable had never been set, so CI regenerated a disallow-all robots.txt on every deploy. That is now set and verified live — 37 of the 38 flagged URLs return 200 to Googlebot.This PR fixes the other half. All 35 URLs still redirect to the site root, which Google reads as a soft 404. Without this they move from "Blocked" to "Page with redirect / not indexed" rather than recovering.
What changed
One destination per section — 29 rules covering all 134 historical campaign doc paths. Each URL now reaches the page that absorbed its content instead of the root.
Both
netlify.tomlandpublic/_redirectsare generated from one table and verified identical.public/_redirectsis what actually serves; the site is on Cloudflare Workers static assets.Two constraints this design satisfies
Cloudflare's 100 dynamic-rule cap. Cloudflare counts every cross-host redirect as dynamic, not just splats and placeholders. An intermediate version of this branch enumerated one rule per page and was rejected at deploy:
Maximum number of dynamic _redirects rules limit of 100 exceeded. Measured correctly,6de16d3had 83 dynamic rules and deployed;02d721chad 142 and did not. This version has 33. Do not expand the block back to one rule per page.Rule precedence. An earlier version listed specific paths above their parent wildcards and relied on first-match-wins. On the preview deploy the parent wildcard won anyway —
/checkout/paymentlanded on the generic checkout page, and three other rules silently lost their anchors. Cloudflare's docs say first-match applies, so this is unexplained. The section shape makes it moot: each section's exact rule and its splat share one destination, so which matches first cannot change the result.Trade-off
Anchor granularity.
/checkout/paymentlands on the checkout page rather than#payment-methods. Section-level anchors are kept where a whole section maps to one (cart-summary→#cart-summary,package-toggle→#order-bumps). Reaching the right page is what resolves the soft 404; the anchor was a nicety, and buying it back would breach the rule cap.Deliberate gaps
fomoinsrc/. There is deliberately no/utilities/*catch-all that would swallow it; the three surviving utilities are listed individually. Cloudflare's_redirectscannot express 410, so a real 404 vianot_found_handlingis the cleanest signal.campaign-cart/cart-systemis unlisted — the existingcampaign-cart/:pathrule already rewrites it into/docs/campaigns/, which these rules then resolve. Verified end-to-end below.Verification
Static, before pushing: TOML parses, no duplicate
from, both files identical across all 29 rules, no rule catching a still-live page, no section with conflicting destinations, all 134 historical paths covered,npm run checkpasses.Live, against the Cloudflare preview deploy:
/docs/campaigns,api,api/carts/cartsCreate,page-kit,templates,admin-api) return 200.campaign-cart/cart-system→/docs/campaigns/cart-system→#cart-operations.Not covered here
guides.nextcommerce.comis also flagged; confirmed it should stay unindexed. Mark as intentional in Search Console.cart-sdk.nextcommerce.com/latest/documents/Reference_URL_Parameters404s — a stale inbound link, now at/latest/reference/url-parameters/.🤖 Generated with Claude Code