Netlify → Cloudflare Pages
Move a Netlify project to Cloudflare Pages while translating builds, environment values, redirects, headers, forms, functions, domains, access, and a measured rollback path.
Should you make this move?
Both platforms have a case. Compare what you gain with what you give up before scheduling the cutover.
Netlify
- Excellent frontend deployment ergonomics combine previews, forms, functions, and add-ons
- Managed deployments reduce infrastructure and release-management work
- Platform-specific services and bandwidth or build costs can become limiting
- Runtime assumptions, platform services, and pricing models shape the architecture
Cloudflare Pages
- Static sites deploy globally with simple previews and strong Cloudflare integration
- Managed deployments reduce infrastructure and release-management work
- Pages-specific workflows and Functions constraints can limit complex full-stack applications
- Runtime assumptions, platform services, and pricing models shape the architecture
Cloudflare Pages: Static sites deploy globally with simple previews and strong Cloudflare integration. This removes a major source-side concern: Platform-specific services and bandwidth or build costs can become limiting.
What you lose: Excellent frontend deployment ergonomics combine previews, forms, functions, and add-ons. What you inherit: Pages-specific workflows and Functions constraints can limit complex full-stack applications.
Know the shape of the move.
This timeline assumes
- Profile: one Git-connected project with one production domain, at most 20,000 deploy files, no asset larger than 25 MiB, up to 20 environment variables, five serverless functions, two forms, and 500 redirects.
- The destination is Cloudflare Pages. A move to Workers Static Assets or a framework-specific Workers adapter requires a different runtime plan.
- You control the Git repository, Netlify owner account, Cloudflare account and zone, DNS, environment values, third-party APIs, and production monitoring.
- The Netlify project remains deployed and billable through the seven-day rollback window.
- Every Netlify-specific feature is assumed non-portable until it has an explicit Cloudflare implementation and production-like preview test.
What survives the move.
“Partial” and “manual” are not footnotes. They are work that must be scheduled and verified.
| Item | Outcome | Impact | What happens | Mitigation |
|---|---|---|---|---|
| Static build output | clean | low | A compatible build command and output directory can deploy to Cloudflare Pages, subject to Pages file-count, file-size, build-time, and framework limits. | Run the exact production build in a clean environment and compare file count, largest file, routes, and asset hashes. |
| Git-connected continuous deployment | clean | medium | Cloudflare Pages can connect to the Git repository and produce production and preview deployments, but branch controls and deploy hooks must be configured again. | Map production branch, preview rules, build watch paths, deploy hooks, and required checks explicitly. |
| _redirects file | partial | high | Cloudflare states that a Netlify _redirects file can move to Pages, but Pages supports fewer advanced conditions and limits the file to 2,000 static plus 100 dynamic rules. | Lint every rule against Pages support, move excess rules to Bulk Redirects, and test order, splats, placeholders, proxies, and status codes. |
| Redirects in netlify.toml | manual | critical | Cloudflare instructs users to convert netlify.toml redirects into a _redirects file. Netlify country, language, role, cookie, query, signed proxy, and domain-level behavior may not have a Pages equivalent. | Translate supported rules and implement unsupported routing with Cloudflare Redirect Rules, Bulk Redirects, or Workers after an explicit design review. |
| Custom response headers | partial | critical | Static header rules can move to a Pages _headers file, but Cloudflare does not apply that file to responses generated by Pages Functions; redirects also run before headers. | Move static rules, emit function and SSR headers in code, and compare security, cache, CORS, and content headers route by route. |
| Netlify Functions | manual | critical | Both products use a functions directory, but handler syntax, request context, runtime APIs, configuration, bundling, limits, and response objects differ. | Rewrite each function for Pages Functions or Workers, map secrets and bindings, and run contract tests with production-like requests. |
| Netlify Edge Functions | manual | critical | Netlify Edge Functions do not deploy as Pages Functions unchanged even though both are edge runtimes. | Rewrite middleware and edge routes for Workers semantics and verify ordering, geolocation, cookies, caching, and origin behavior. |
| Netlify Forms and submissions | lost | critical | Cloudflare explicitly says to remove data-netlify attributes and implement form handling with a Pages Function plus a database or external service. Existing submissions do not migrate automatically. | Export historical submissions, build a new handler and storage/notification path, add spam protection, and test success, validation, duplicate, and failure cases. |
| Environment variables and secrets | manual | critical | Netlify variables can have team/site scope, deploy-context values, and build/function scopes. Cloudflare values and bindings must be recreated with environment-specific ownership. | Export keys and contexts securely, never commit values, map build versus runtime access, rotate secrets after migration, and verify no preview receives production-only credentials. |
| Netlify build plugins and post-processing | lost | high | Netlify build plugins, snippet injection, post-processing, and plugin-provided configuration do not execute on Cloudflare Pages. | Replace each effect with framework code, build scripts, Cloudflare configuration, or an explicit decision to remove it. |
| Netlify Identity and role-gated routes | lost | critical | Cloudflare suggests Zero Trust for access management, but it is not a drop-in migration of Netlify Identity users, tokens, signup flows, or application roles. | Choose a destination identity system, export permitted user data, design account migration and password reset, and test authorization separately. |
| Deploy previews | partial | medium | Cloudflare Pages provides preview deployments, but URLs, access policy, branch behavior, environment values, and third-party allowlists change. | Configure preview branch rules and access, use non-production secrets, and update callback and allowlist URLs. |
| Large files and very large sites | partial | high | Cloudflare Pages documents 20,000 files on Free, up to 100,000 on eligible paid plans, and a 25 MiB maximum per asset. | Measure the built artifact before migration; move oversized assets to R2 or another designed store and enable the required paid-plan setting for higher file counts. |
| Historical deploys, analytics, logs, form entries, and audit data | lost | medium | Operational history remains in Netlify and is not imported into the new Cloudflare project. | Export required reports, submissions, logs, deploy identifiers, configuration, and billing records before account cleanup. |
| Netlify subdomain and platform URLs | lost | medium | The netlify.app hostname remains a Netlify resource; Cloudflare creates a different pages.dev hostname. | Keep the Netlify project during rollback and configure an intentional long-term redirect before eventual retirement if the hostname has traffic. |
Where each thing goes.
| Source | Destination | Method | Notes |
|---|---|---|---|
| Netlify production branch | Cloudflare Pages production branch | manual | Confirm branch protection, preview behavior, and who can trigger production. |
| Build command | Cloudflare Pages build command | automatic | Use the same command only after it succeeds in Cloudflare's build image with the mapped runtime and variables. |
| Publish directory | Cloudflare Pages build output directory | automatic | Compare the final directory path from the project base, especially in monorepos. |
| netlify.toml build settings | Pages project or Wrangler configuration | transform | Treat the chosen configuration source as authoritative and reconcile it with dashboard values. |
| Netlify _redirects | Pages _redirects | transform | Supported simple rules can transfer; verify order, limits, advanced conditions, and function-route exceptions. |
| netlify.toml redirects | _redirects, Bulk Redirects, Redirect Rules, or Worker routing | manual | Choose the Cloudflare layer based on count and feature support; do not mechanically flatten unsupported conditions. |
| Netlify headers | Pages _headers or function Response headers | transform | Function and SSR responses must set their own headers. |
| Netlify Function handler | Pages Function onRequest handler or Worker fetch handler | manual | Translate request, context, environment, routing, response, errors, logs, and platform API usage. |
| Netlify Forms | Pages Function plus durable storage and notifications | manual | Remove data-netlify and replace spam, validation, file, notification, and submission-management behavior. |
| Build-scope environment variable | Pages build variable | manual | Map production and preview values without exposing production secrets to untrusted previews. |
| Function-scope environment variable | Pages Function variable, secret, or binding | manual | Choose text variable, encrypted secret, KV, D1, R2, service, or other binding by actual runtime behavior. |
| Custom domain | Pages custom domain association plus DNS record | manual | Associate the domain in the Pages dashboard before pointing a CNAME; a manual CNAME alone can cause a 522. |
| Netlify access control | Cloudflare Access or application authentication | manual | Separate preview protection from end-user application identity. |
| Deploy hook | Cloudflare deploy hook or CI deployment | manual | Update every caller, shared secret, branch target, retry policy, and monitoring check. |
Make the move recoverable.
Create the source-of-truth backup
Capture enough Netlify state to rebuild or restore the known-good production deployment without relying on memory.
- Record the repository and commit, production branch, base directory, package manager, runtime versions, build command, publish directory, functions directory, plugins, deploy contexts, ignore rules, and build-image settings.
- Download or archive the repository, netlify.toml, _redirects, _headers, function and edge-function source, lockfile, and the known-good production artifact if available.
- Export the names and context/scope mapping of all environment variables; copy secret values only into the approved secret store, never the repository or work log.
- Inventory domains, DNS records, certificates, redirects, headers, forms and submissions, functions, scheduled/background jobs, identity, access, webhooks, deploy hooks, plugins, analytics, logs, add-ons, and billing.
- Record the current production deploy ID, URL, timestamp, commit, checksums for critical assets, and a critical-URL response/header baseline.
- Export historical form submissions and operational reports that must survive Netlify cleanup.
- Test rollback access by confirming an authorized operator can restore the domain routing and that the known-good Netlify deploy is healthy.
Proof to capture: A dated configuration and feature manifest, redacted environment key matrix, encrypted secret location, production deploy ID, source commit, URL/header baseline, DNS snapshot, export checksums, and measured rollback access check.
Create the Cloudflare preview build
Prove the framework build independently of production routing.
- Create the Pages project from the same repository and production branch while leaving the custom domain on Netlify.
- Set the exact base, build, output, runtime, package-manager, and environment configuration from the inventory.
- Build the recorded production commit and compare generated file count, largest asset, route list, and critical asset hashes.
- Protect preview access where required and record the pages.dev deployment URL and ID.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Translate redirects and headers
Preserve routing, security, CORS, and cache behavior within Cloudflare's supported layers.
- Copy supported _redirects rules; translate netlify.toml rules only after classifying conditions, rewrites, proxies, domains, and counts.
- Move static headers into _headers and put function or SSR response headers in code.
- Check Pages limits and route ownership; redirects and _headers do not apply normally to responses owned by Pages Functions.
- Run the complete baseline URL set and compare status, final URL, chain length, cache, security, CORS, content type, and robots headers.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Rewrite functions and forms
Replace Netlify runtime behavior with testable Pages Functions or Workers behavior.
- Write one contract per function: method, route, inputs, authentication, environment, side effects, response, headers, timeout, retries, logs, and failure behavior.
- Translate handlers to the chosen Cloudflare runtime and bind only the required secrets or services.
- Replace Netlify Forms with an explicit POST handler, validation, spam protection, storage, notification, privacy, and export path.
- Run success, validation, authentication, duplicate, timeout, upstream failure, and retry tests against preview.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
Recreate environment and deployment operations
Keep production and preview behavior intentional and least-privileged.
- Create a key-only mapping from Netlify scope and context to Cloudflare build, preview, production, function, secret, or binding.
- Enter values through the approved dashboard or secret command and verify they never appear in build output, logs, or client bundles.
- Recreate deploy hooks, callback URLs, allowlists, access rules, alerts, log destinations, and status monitoring.
- Rotate credentials that were exposed to both platforms after the migration stabilizes.
Proof to capture: Save the input, output, command or tool settings, warnings, and final item counts.
The things most likely to hurt.
These are operating limits. Treat every “Stop if” condition as a blocked migration, not a suggestion.
Treating a successful build as platform parity
A static homepage can work while forms, functions, redirects, headers, identity, hooks, or environment-specific behavior fails.
- Consequence
- Production appears healthy until a user or integration reaches the missing feature.
- Mitigation
- Inventory every Netlify feature and require one destination owner and contract test for each.
Stop if: Any production-critical platform feature is unmapped or untested.
Copying unsupported redirect behavior
Cloudflare Pages does not support every Netlify redirect condition or rewrite and imposes rule limits.
- Consequence
- Users can reach the wrong locale, role, proxy, or application path, or important rules may be ignored.
- Mitigation
- Classify rules by feature and count, choose the correct Cloudflare layer, and compare the full route baseline.
Stop if: A rule relies on unsupported query, country, language, cookie, domain-level, or role behavior without a tested replacement.
Assuming _headers covers functions
Cloudflare documents that _headers rules do not apply to Pages Function responses.
- Consequence
- Security, cache, CORS, and content-type protections can disappear on dynamic routes.
- Mitigation
- Set headers in function code and contract-test every dynamic route.
Stop if: Any function response lacks its required security, cache, CORS, or content header.
Mis-scoping secrets between preview and production
Netlify deploy contexts and scopes do not map automatically to Cloudflare environments and bindings.
- Consequence
- Production credentials can leak to previews, or production functions can start with missing or test values.
- Mitigation
- Use a key-only mapping, least privilege, separate preview values, and a redaction scan of bundles and logs.
Stop if: A production-only value is available to an untrusted preview or any runtime key is missing.
Pointing DNS before associating the Pages domain
Cloudflare warns that manually pointing a CNAME at pages.dev without first associating the custom domain can return a 522.
- Consequence
- The public domain can become unavailable during cutover.
- Mitigation
- Complete the Pages custom-domain workflow first, confirm readiness, then change the recorded DNS record.
Stop if: The domain is not active or ready in Pages, certificate issuance is blocked, or CAA prevents issuance.
Exceeding Pages artifact limits
Pages documents file-count, 25 MiB single-file, build-time, redirect, and header-rule limits.
- Consequence
- Builds can fail or large assets and rules can be omitted.
- Mitigation
- Measure the production artifact and configuration before project creation and choose R2, paid limits, Bulk Redirects, or Functions where required.
Stop if: The built artifact or configuration exceeds the selected Pages plan and no approved alternative exists.
Do the work in this order.
- Day 1 · inventory-backup
Inventory Netlify and prove rollback access
3–5 hours active1 day elapsedAllow report and submission exports to finish waiting- Capture build, repository, artifact, environment, runtime, routing, domain, feature, integration, access, monitoring, and billing state.
- Classify every Netlify-specific feature as direct, translate, rebuild, replace, or retire.
- Confirm the known-good Netlify deploy and domain rollback path.
Stop / go checkpointIs every production behavior accounted for?
Go when: The feature inventory has no unknown owner and Netlify can be restored without changing source code.
Stop when: An undocumented function, form, identity flow, plugin, hook, secret, or redirect remains.
- Day 2 · pages-preview
Build the same commit on Pages
2–4 hours active1 day elapsed30–90 minutes for build and preview cycles waiting- Create the Pages project, configure build settings and safe preview values, and deploy the recorded production commit.
- Measure files, asset sizes, route output, build warnings, and critical asset hashes.
- Protect preview access and update test-only third-party allowlists.
Depends on: inventory-backup
Stop / go checkpointIs the artifact equivalent and within limits?
Go when: The clean Pages build matches the expected artifact and all plan limits are satisfied.
Stop when: Files, assets, routes, runtime, or build warnings differ without explanation.
- Days 3–4 · translate-platform
Translate routing, runtime, forms, and operations
4–10 hours active1–2 days elapsedAllow external callback, secret, and access-policy updates to propagate waiting- Translate redirects and headers into the correct Cloudflare layers.
- Rewrite and contract-test functions, edge behavior, forms, identity, plugins, hooks, and integrations.
- Map variables, secrets, bindings, preview controls, logs, alerts, and deploy operations.
Depends on: pages-preview
Stop / go checkpointDoes preview satisfy every feature contract?
Go when: Every inventory row has a passing automated or manual acceptance result.
Stop when: Any critical runtime behavior, side effect, secret, access control, or failure path is untested.
- Day 5 · parity-rehearsal
Run parity checks and rehearse routing
2–4 hours active1 day elapsedObserve preview for at least four hours under production-like checks waiting- Run URL, redirect, header, asset, function, form, authentication, analytics, webhook, and performance checks.
- Associate the custom domain in Pages without moving public traffic and resolve CAA or certificate blockers.
- Rehearse and time the exact DNS cutover and rollback checklists.
Depends on: translate-platform
Stop / go checkpointCan cutover and rollback fit the window?
Go when: All blocking parity checks pass and both routing paths are timed with working credentials.
Stop when: The Pages domain is not ready or any operator, credential, baseline, or monitoring path is missing.
- Day 6 · cutover
Deploy the final commit and move the domain
2–3 hours active2–4 hours elapsedObserve DNS, certificate, edge cache, and third-party callbacks during the window waiting- Freeze configuration, deploy the final commit, and run blocking checks on pages.dev.
- Change the recorded domain routing only after Pages shows the domain ready.
- Run production checks twice and monitor errors, logs, submissions, and external callbacks.
Depends on: parity-rehearsal
Stop / go checkpointKeep Cloudflare live or restore Netlify?
Go when: Every production blocking check passes twice and real form/function/analytics receipts are confirmed.
Stop when: A blocking check fails or cannot be corrected and reverified within 60 minutes.
- Days 7–12 · monitor
Monitor through the rollback window
20–30 minutes daily activeSeven days after cutover elapsedSeven-day observation period waiting- Review availability, errors, 404s, redirects, functions, forms, authentication, cache, analytics, and billing.
- Compare production baselines and investigate user or integration reports.
- Approve cleanup only after the signed report and source-retirement review.
Depends on: cutover
Stop / go checkpointCan Netlify be retired?
Go when: Seven stable days pass, no rollback trigger remains, and all operational owners accept Cloudflare.
Stop when: Critical failures, lost submissions, missing traffic, security regressions, or unexplained costs remain.
Cut over with a way back.
Cutover
Move the production domain to the final verified Pages deployment while preserving a one-change rollback to Netlify.
- Freeze repository merges and platform configuration; record UTC time, final commit, Netlify deploy ID, and Cloudflare deployment ID.
- Deploy the final commit to Pages and run all blocking checks on the pages.dev hostname with production configuration.
- Confirm the custom domain is associated and ready in Pages, certificate and CAA requirements pass, and the before-state DNS record is recorded.
- Change only the required domain record or origin route; do not delete the Netlify domain or project.
- Run production checks twice from outside the operator network, including real safe form, function, webhook, analytics, redirect, header, and access tests.
- Monitor both platform logs and public synthetic checks for at least two hours before announcing completion.
Proof to capture: Freeze timestamp, commit and deployment IDs, before/after DNS records, Pages domain-ready state, two passing reports, form/function receipts, analytics event, external callback confirmation, and named operator approval.
Rollback
Restore the recorded Netlify production deploy and domain route without losing Cloudflare incident evidence or user submissions.
- Declare rollback, pause further Cloudflare configuration changes, and preserve deployment, function, request, form, and error logs.
- Restore the exact recorded DNS or origin route to the still-active Netlify custom domain.
- Run the critical URL, redirect, header, function, form, identity, analytics, and webhook baseline against Netlify.
- Export and reconcile any Cloudflare-only form submissions, writes, queues, or external side effects before reopening normal operations.
- Communicate the rollback owner, cause, restored deploy ID, evidence location, and next decision time.
Proof to capture: The production domain serves the recorded Netlify deploy, all baseline checks pass, post-cutover writes are reconciled, and only Netlify receives new production traffic.
- Availability, SSL, DNS, redirect, header, function, form, identity, analytics, or webhook blocking checks fail after one correction attempt.
- Production secrets are missing, exposed to previews, or present in client output.
- User submissions or state-changing requests are lost, duplicated, or cannot be reconciled.
- A critical regression cannot be corrected and fully reverified within 60 minutes.
- Unexpected error rate or cost exceeds the agreed threshold.
Prove the migration worked.
Every blocking check must pass. Capture the evidence before cleanup begins.
| Pass | ID | Check | Method | Expected result | Evidence |
|---|---|---|---|---|---|
BLD-01Blocking | Final artifact parity and limits | Compare final commit, build command, route list, file count, largest file, critical asset hashes, and warnings. | The intended commit builds cleanly; output is explained and within selected Pages limits. | Attach build logs and artifact comparison. | |
URL-01Blocking | Critical URL response parity | Request the baseline URL set against Netlify and Cloudflare; compare status, final URL, chain, body marker, and timing. | Every route has the intended result with no unexplained 404, loop, chain, or rewrite difference. | Attach the side-by-side URL report. | |
HDR-01Blocking | Header parity | Compare cache, security, CORS, content type, content encoding, robots, canonical-link, and custom headers on static and function routes. | Every required header is present once with deliberate Cloudflare-specific differences documented. | Attach raw response headers for the route matrix. | |
RDR-01Blocking | Rule behavior and limits | Test every static rule and representative dynamic, splat, placeholder, proxy, conditional, and domain case. | Rules resolve once to the approved destination; unsupported Netlify behavior has a tested replacement. | Attach the redirect test output and rule-count summary. | |
FUN-01Blocking | Runtime contract suite | Run success, validation, authentication, method, timeout, upstream failure, side-effect, retry, and CORS cases for every function. | Responses, headers, logs, side effects, and failure behavior meet the recorded contract. | Attach test results and redacted log/request IDs. | |
FRM-01Blocking | Submission lifecycle | Submit valid, invalid, duplicate, spam-like, and upstream-failure cases through every production form. | Valid data is stored and notified once; invalid or abusive data is rejected; failure is visible and recoverable. | Attach receipts, stored record IDs, notifications, and redacted logs. | |
SEC-01Blocking | Environment scope and leakage | Compare the key matrix by environment and scan client assets, build logs, and function logs for known values and patterns. | Every required value exists only in its approved scope and no secret appears in client output or logs. | Attach the key-only matrix and redacted scan result. | |
DOM-01Blocking | DNS, SSL, and hostname behavior | Resolve from multiple public resolvers and request canonical, alternate, pages.dev, and netlify.app hostnames over HTTPS. | The custom domain serves Pages with a valid certificate; alternate and platform hostnames follow the approved policy. | Attach DNS answers, certificate details, HTTP results, and Pages domain status. | |
ACC-01Blocking | Preview and application authorization | Test anonymous, approved preview, denied preview, authenticated user, expired session, and forbidden-role cases where applicable. | Preview protection and application authorization match separate documented policies. | Attach result captures without tokens. | |
OPS-01Blocking | Deploy, monitoring, and callback paths | Trigger one safe preview deploy and one production-safe operational event; inspect alerts, logs, analytics, webhooks, and status checks. | The correct environment deploys, every intended observer receives one event, and no obsolete Netlify hook mutates production. | Attach deployment, event, callback, and monitoring IDs. | |
RLB-01Blocking | Netlify restore readiness | Confirm the known-good deploy, domain association, environment, platform features, credentials, and timed routing steps. | An authorized operator can restore Netlify inside the agreed window without code changes. | Attach the rollback rehearsal duration and readiness checklist. |
Remove the scaffolding safely.
Safe after: Seven stable production days, a signed report, no unreconciled writes or submissions, and explicit closure of the Netlify rollback window.
- Export final Netlify deploy, build, form, function, analytics, audit, add-on, DNS, environment-key, and billing records required for retention.
- Archive the known-good deploy manifest, repository commit, configuration, URL/header baseline, and rollback evidence with checksums.
- Disable obsolete Netlify deploy hooks, webhooks, build notifications, identity flows, forms, functions, plugins, and add-ons one category at a time.
- Rotate secrets that existed on both platforms and revoke migration-only users, tokens, deploy keys, and temporary access policies.
- Redirect the used netlify.app hostname deliberately if required; otherwise document its retirement and monitoring.
- Remove the Netlify custom domain only after confirming Cloudflare ownership and rollback-window closure.
- Cancel Netlify paid services last, after a second person confirms archives, submission retention, billing, and recovery instructions.
- Update architecture, incident, deployment, secret, domain, form, function, access, monitoring, ownership, and next-review documentation.
Built to be reviewed.
Tested 2026-07-18. Next scheduled review: 2026-10-18.
- Cloudflare: Migrating from Netlify to PagesAccessed 2026-07-18
- Cloudflare Pages redirectsAccessed 2026-07-18
- Cloudflare Pages headersAccessed 2026-07-18
- Cloudflare Pages custom domainsAccessed 2026-07-18
- Cloudflare Pages limitsAccessed 2026-07-18
- Netlify environment variablesAccessed 2026-07-18
- Netlify file-based configurationAccessed 2026-07-18