Featured image for How to Deploy a SPA with Free Cloudflare: Complete Guide

How to Deploy a SPA with Free Cloudflare: Complete Guide

Published on:

Reading time: 9 min

Topic: Technology

Author: Leandro Valencia

#cloudflare#spa#deploy#frontend#serverless#cdn

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

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:

  1. Connect the Git repository.
  2. Choose the framework.
  3. Define the build command.
  4. Specify the output folder.
  5. 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)

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:

  1. Deploy the frontend on Pages first.
  2. Correctly resolve SPA routes.
  3. Separate public variables from secrets.
  4. Use Functions or Workers only for dynamic logic.
  5. Store large files in R2.
  6. Protect login, registration, and API.
  7. Measure consumption, errors, and performance.
  8. 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

How to Deploy a SPA with Free Cloudflare: Complete Guide