fix(notion): bypass Cloudflare IP block via loadCachedPageChunkV2 + throttle/cache - #768
Merged
Conversation
Notion's unofficial /api/v3 endpoints (loadPageChunk) are hard-blocked by Cloudflare with a 403 "Attention Required" page when called from datacenter egress IPs, which was 500'ing every track/problem page in production. - Add getNotionClient() that authenticates with NOTION_TOKEN_V2 / NOTION_ACTIVE_USER so requests aren't challenged; reuse a singleton. - Cache recordMaps (10m TTL) with a never-expiring stale fallback so a transient Notion/Cloudflare failure degrades to slightly-stale content instead of a 500, and to cut the request volume keeping our IP flagged. - Retry getPage/getBlocks with backoff. - Guard NotionRenderer against an empty/missing recordMap. - Document the new env vars in .env.example and turbo.json. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…block Authenticating alone doesn't help: Cloudflare blocks our datacenter egress IP at the edge (403) before the token_v2 cookie is even checked, on the endpoints notion-client uses (loadPageChunk, syncRecordValues, queryCollection). loadCachedPageChunkV2 is NOT blocked from the cluster and returns the full page recordMap in one request, so fetch through it via the client's public fetch() instead of getPage(). The block is IP-reputation based and re-triggers under request bursts, so add a global concurrency cap + min-gap throttle and back off harder on 403s; combined with the existing 30m cache + stale fallback this keeps request volume low enough to stay unblocked. Route search/AddTracks through the same path and guard against null recordMaps. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add opt-in proxy support so all Notion API calls can tunnel through a clean (non-datacenter) IP via https-proxy-agent when NOTION_PROXY_URL is set. This is the fully-robust fallback for Notion's IP-reputation-based Cloudflare block and also covers the endpoints loadCachedPageChunkV2 can't (images/embedded DBs). Inert when the env var is unset. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
hkirat
force-pushed
the
fix/notion-cloudflare-block
branch
from
August 5, 2026 11:07
c44c336 to
2314ed4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
https://projects.100xdevs.com/tracks/...was returning HTTP 500 on every track/problem page (root still 200). The k8s cluster is healthy — the failure is an external dependency.Root cause: track/problem content is fetched server-side from Notion's unofficial
/api/v3API vianotion-client. Notion sits behind Cloudflare, and requests from our DigitalOcean datacenter egress IP are hard-blocked with a 403 "Attention Required" Cloudflare page on the endpointsnotion-clientuses (loadPageChunk,syncRecordValues,queryCollection). That 403 became a Next.js 500.Verified: a valid
token_v2returns 200 from a residential IP but still 403 from the pod — Cloudflare blocks at the edge on IP reputation, before the cookie is checked. So auth alone does not fix it.Key finding:
loadCachedPageChunkV2(andgetRecordValues) are not blocked from the cluster andloadCachedPageChunkV2returns the full page recordMap in a single request.Changes
getNotionClient()(auth viaNOTION_TOKEN_V2/NOTION_ACTIVE_USER), used at all call sites (tracks,pdf,search,AddTracks).fetchNotionPagenow fetches throughloadCachedPageChunkV2(via the client's publicfetch()) instead of the blockedgetPage().NotionRendererguards against an empty/missing recordMap;search/AddTracksguard against null.NOTION_TOKEN_V2/NOTION_ACTIVE_USERin.env.exampleandturbo.json.Deploy note
Set
NOTION_TOKEN_V2(thetoken_v2cookie from a logged-in notion.so session) indailycode-secret, then rebuild/redeploydailycode. Auth is optional for the endpoint swap to work, but recommended for private-page access.Known limitation: Notion-hosted images (
getSignedFileUrls) and embedded databases (queryCollection) use endpoints that are still IP-blocked, so those may render degraded. Text/code content (the bulk of the course) is restored. The fully-robust follow-up is routing Notion egress through a clean/non-datacenter IP (proxy).Test plan
turbo buildpasses/tracks/Bridges/Bridges-1returns 200 with contentGenerated with Devin
Update: built-in proxy support
Also added optional
NOTION_PROXY_URL. When set, all Notion calls tunnel through that proxy (viahttps-proxy-agent), routing egress off the flagged datacenter IP — the fully-robust fix that also restores images (getSignedFileUrls) and embedded DBs (queryCollection). Inert when unset.