
How to Deploy a SPA with Free Cloudflare: Complete Guide
Published on:
Reading time: 9 min
Topic: Technology
Author: Leandro Valencia
Learn how to use Cloudflare's free tier to deploy, protect, and operate a SPA or small web app with Pages, Functions, Workers, and R2.
Table of Contents
- Strategic Summary
- 1. When to use Pages vs Workers
- 6. Use custom domain at the end of deployment
- 7. Protect origin and API
- 9. Use R2 for large files
- 10. Avoid aggressive caching during development
- 11. Metrics you should monitor
- Recommended architecture to start
- Conclusion
Strategic Summary
| Need | Tool | Recommendation |
|---|---|---|
| Deploy frontend | Cloudflare Pages | Connect Git repo and deploy automatically |
| Serve static files | Pages CDN | Take advantage of free and unlimited asset requests |
| Manage domains | Cloudflare DNS | Use custom domain and proxied records |
| Protect application | WAF Custom Rules | Protect login, registration, and sensitive endpoints |
| Create small API | Pages Functions or Workers | Use them for lightweight logic and simple endpoints |
| Store files | R2 | Separate images, PDFs, and large files from frontend |
| Store simple data | KV or D1 | Use them for configuration, sessions, or prototypes |
| Protect forms | Turnstile | Add it only where abuse exists |
| Handle SPA routes | _redirects or _routes.json |
Send unknown routes to index.html |
| Manage secrets | Variables and Secrets | Never include private keys in frontend bundle |
| Validate changes | Preview Deployments | Review each pull request before production |
| Control costs | Limits and analytics | Measure Pages Functions and Workers separately |
1. When to use Pages vs Workers
For a traditional SPA, the easiest starting point is Cloudflare Pages.
Pages is suitable when your application compiles to static files:
npm run build
and generates a folder like:
dist/
The workflow would be:
- Connect the Git repository.
- Choose the framework.
- Define the build command.
- Specify the output folder.
- Deploy automatically with every change.
Cloudflare Pages allows you to configure commands, output folders, environment variables, and presets for different frameworks. (Build configuration)
Use Workers when you need:
- A custom API.
- Advanced middleware.
- Authentication.
- Request transformation.
- Logic executed at the edge.
- A project combining frontend and backend in a single deployment.
The practical recommendation is:
Start with Pages for the frontend and only add Workers or Pages Functions when you have a concrete need for backend.
2. Important free tier limits
The free Pages plan allows up to 500 monthly builds, one simultaneous build, 20,000 files per site, and a maximum size of 25 MiB per file. It also allows unlimited preview deployments. (Pages Limits)
This is sufficient for many personal applications, MVPs, and small projects.
However, it's not advisable to upload directly to Pages:
- Large videos.
- Installers.
- Backups.
- Heavy multimedia files.
- Large image collections.
For that, it's better to use R2 or specialized storage and leave Pages exclusively for the application.
Uncommon tip
Count the files generated by your build:
Get-ChildItem -Recurse dist | Measure-Object
An application may seem small in megabytes but exceed the file limit due to source maps, translations, fonts, or duplicate assets.
3. The classic SPA problem: deep routes
A SPA may work perfectly when navigating from /, but show a 404 error if the user enters directly:
/dashboard
/settings/profile
/projects/123
This happens because the server looks for a physical file named dashboard or settings/profile, although those routes should be interpreted by the frontend router.
The solution is to redirect unknown routes to index.html.
In many Pages projects, you can create a _redirects file inside the public folder:
/* /index.html 200
The code 200 indicates that the browser should receive the content of index.html without converting the route into a visible redirect.
Uncommon tip
Don't apply this fallback indiscriminately to static files or APIs. A poorly configured setup can turn real errors like these:
/api/users
/assets/logo.svg
/favicon.ico
into HTML responses from index.html.
Always test:
- The main route.
- An internal route.
- A non-existent route.
- Browser refresh.
- A directly shared link.
- Access from a mobile device.
4. Environment variables: public doesn't mean secret
A SPA runs in the browser. Therefore, any variable included during the build may end up being visible to the user.
It's valid to expose:
VITE_API_URL
NEXT_PUBLIC_API_URL
PUBLIC_SUPABASE_URL
But you should never expose:
DATABASE_PASSWORD
STRIPE_SECRET_KEY
JWT_PRIVATE_KEY
CLOUDFLARE_API_TOKEN
Public variables can be used to configure the frontend. Secrets should remain in Pages Functions, Workers, or your backend.
Cloudflare allows configuring different variables for production and preview from the project panel. (Pages Bindings)
Uncommon tip
Create two separate environments:
Preview → Test API
Production → Real API
This way, you can review a pull request without the test frontend modifying real data.
Also, explicitly set the Node.js version via .nvmrc, .node-version, or NODE_VERSION. This prevents a change in the build environment from unexpectedly breaking the deployment. (Pages Build Image)
5. Don't put the entire API inside a SPA
A SPA shouldn't communicate directly with services that require private keys.
Insecure architecture:
SPA → External service using secret key
Recommended architecture:
SPA → Worker or Pages Function → External service
The Worker can:
- Validate received data.
- Check authentication.
- Apply rate limiting.
- Hide credentials.
- Normalize responses.
- Log errors.
Pages Functions run as Workers and their requests count toward the Workers quota. In the free plan, the combined quota is 100,000 daily requests. (Pages Functions Pricing)
Uncommon tip: don't invoke Functions for everything
If you add a Function to a Pages project without configuring routes, you might end up executing dynamic logic for requests that should only serve static files.
Configure routes so the Function only responds to:
/api/*
and leave assets as static content. Cloudflare allows defining inclusion and exclusion routes to control which requests invoke Functions. (Pages Functions Routing)
6. Use custom domain at the end of deployment
First test the application at:
my-app.pages.dev
Then connect:
app.example.com
This makes it easy to separate:
app.example.com → production
staging.example.com → testing
api.example.com → API
assets.example.com → large files
Enable Cloudflare proxy for HTTP and HTTPS traffic, but leave DNS-only for services not compatible with web proxy, such as some mail records.
7. Protect origin and API
If the frontend is on Pages but the API lives on another server, also protect that origin.
Recommended measures:
- Don't unnecessarily reveal the backend IP.
- Apply authentication to the API, not just the frontend.
- Validate CORS.
- Limit requests to
/api/login,/api/register, and/api/reset-password. - Use Turnstile on attacked forms.
- Apply Managed Challenge before blocking users.
Cloudflare's custom rules allow actions like blocking or challenging requests. In the free plan, there are quantity and functionality limits, so it's advisable to reserve them for critical routes. (Custom Rules)
8. Take advantage of Preview Deployments
One of the most useful features for small teams is having a preview URL per pull request.
The recommended workflow:
Pull request
↓
Preview deployment
↓
Visual and functional testing
↓
Merge to production
This is especially important for SPAs because it allows detecting:
- Routes returning 404.
- Missing environment variables.
- CORS errors.
- Broken changes on mobile.
- Cache issues.
- Authentication failures.
Uncommon tip
Include a small visual label in your application indicating the environment:
Preview · commit a81f2c
This way, no one confuses a test URL with production during a review.
9. Use R2 for large files
A SPA bundle should contain only what's necessary to run the application.
Keep outside of Pages:
- Images uploaded by users.
- Videos.
- PDFs.
- Project files.
- Backups.
- Generated exports.
R2 is designed for object storage and can be used with Workers or your own API. In Cloudflare's current offering, the free plan includes a storage and operations quota for R2. (Cloudflare Plans)
A simple architecture would be:
SPA → Worker → R2
The Worker generates controlled URLs and prevents the bucket from being a completely open access point.
10. Avoid aggressive caching during development
Cloudflare can cache frontend files for a long time. This is positive in production but can be frustrating when you're developing.
To avoid problems:
- Use versioned names for CSS and JavaScript.
- Don't cache private responses.
- Purge specific URLs instead of purging everything.
- Use Development Mode during rapid changes.
- Check
CF-Cache-Status. - Remember that browser cache may survive a Cloudflare purge.
The free plan allows Cache Rules, cache purging, and cache analytics, albeit with lower limits than higher plans. (Cache Features by Plan)
11. Metrics you should monitor
For a SPA, measuring visits isn't enough.
Observe:
- Main JavaScript load time.
- Percentage of cached responses.
- 404 errors on internal routes.
- 4xx and 5xx API errors.
- Function requests.
- Daily Workers usage.
- CPU time per request.
- Authentication error rate.
- Bundle size.
- Form abandonment rate.
The free Workers plan has a limit of 100,000 daily requests and 10 ms of CPU per request. A small function may work well, but a function that processes images, authenticates complex users, or performs heavy calculations may exceed that limit. (Workers Limits)
Recommended architecture to start
Cloudflare DNS
↓
Cloudflare Pages
↓
Static SPA
↓
Pages Functions / Workers
↓
External database or D1
↓
R2 for files
For an MVP, you can start with:
- Pages for the frontend.
- A Worker or Pages Function for the API.
- Supabase, Neon, or D1 for the database.
- R2 for files.
- Turnstile for sensitive forms.
- Preview Deployments to review changes.
Conclusion
Cloudflare's free tier is especially interesting for deploying SPAs because it combines static hosting, CDN, HTTPS, custom domains, previews, and serverless functions.
The most efficient strategy is:
- Deploy the frontend on Pages first.
- Correctly resolve SPA routes.
- Separate public variables from secrets.
- Use Functions or Workers only for dynamic logic.
- Store large files in R2.
- Protect login, registration, and API.
- Measure consumption, errors, and performance.
- Scale only when a real limit exists.
Cloudflare doesn't automatically replace the entire backend, but it does allow building a functional, secure, and quite economical web application with a modular architecture.
Related Posts
Keep exploring similar content that may interest you
2026 AI Tier List: Why the Harness Matters More Than the Model
An editorial comparison of Cursor, Claude Code, Antigravity, OpenCode, Hermes, VS Code, Orca, and Herder: why in 2026 your competitive edge comes from the AI harness, not just the model.

How to Install and Master the Superpowers Skill in Claude Cowork
Learn how to install Superpowers in Claude Cowork from the official Claude marketplace or from GitHub. Discover the AI skills framework that applies TDD and systematic development so your agent plans, tests, and reviews its own code.

Amplitude and Claude: Complete Product Analytics Guide
Find out if Amplitude is worth it, how to use it step by step to analyze your users' behavior, and how to supercharge it by connecting your data to Claude via MCP to ask questions about your analytics like you would a senior analyst.