Ad Code

I Switched from Vercel to Cloudflare for Next.js

 

this, you’re likely looking for ways to host Next.js websites efficiently, especially given the high costs and vendor locking issues with Vercel. Recently, one of my clients paid $1900 for just one month on Vercel, highlighting the expense, particularly with their per-seat pricing model and additional fees for features like firewalls. This led me to explore Cloudflare Workers, made possible by the OpenNext project, which enables Next.js hosting on various platforms.

In this detailed guide, I’ll walk you through the best practices for setting up, deploying, and managing a Next.js website on Cloudflare Workers and Cloudflare R2.

What is OpenNext?

Before we dive into code, let’s take a moment to explore OpenNext, the engine driving true portability for Next.js applications. OpenNext is an open-source initiative designed to decouple Next.js from the Vercel platform, giving teams the flexibility to self-host their apps across supported platforms like AWS Lambda, Cloudflare Workers, and Netlify. Unlike frameworks such as Remix or Astro, which were built with provider-agnostic deployment in mind, Next.js has historically been tightly coupled with Vercel’s infrastructure. OpenNext changes that by enabling consistent builds and deployments across these three major platforms.

At its core, OpenNext provides a set of adapters that translate your Next.js build output into formats compatible with different serverless and edge platforms. This enables developer teams to deploy production-grade Next.js applications wherever it makes the most sense for their architecture, performance needs, or cost considerations.

The project is actively maintained by contributors across multiple ecosystems and is evolving rapidly to support the full range of Next.js features like middleware, app directory, and edge rendering. While some rough edges are expected during development, OpenNext is already robust enough for real-world use and continues to gain traction in the community.

For Developer teams, this means breaking free from vendor lock-in, optimising for performance at the edge, and regaining control over deployment workflows, all without giving up the power and familiarity of the Next.js framework.

Let’s dive into building a sample application and see OpenNext in action.

Creating a Sample Next.js Website

Let’s build a Next.js app named nextjs-demo with a homepage featuring Next.js Meets Cloudflare and an API route for dynamic content.

mkdir nextjs-cloudflare

npx create-next-app@latest .


Choose TypeScript and the App Router for a modern setup (JavaScript and Pages Router work too). This creates a project with all dependencies.

Build the Homepage

Next, let’s customise the homepage to reflect our deployment context. Open your project and edit app/page.tsx

import Image from 'next/image';

export default function Home() {

  return (

    <div style={{

      display: 'flex',

      justifyContent: 'center',

      alignItems: 'center',

      minHeight: '100vh',

      background: 'linear-gradient(45deg, #00C6FF, #0070f3)', // Gradient background for a vibrant look

      fontFamily: 'Arial, sans-serif',

    }}>

      <div style={{

        backgroundColor: '#ffffff',

        padding: '50px',

        borderRadius: '12px',

        boxShadow: '0 10px 30px rgba(0, 0, 0, 0.2)',

        textAlign: 'center',

        maxWidth: '700px',

        width: '100%',

      }}>

        <h1 style={{ 

          fontSize: '2.5rem', 

          marginBottom: '20px', 

          fontWeight: 'bold', 

          color: '#333', 

        }}>

          <span style={{ color: '#f38020' }}>Cloudflare</span> <span style={{ color: '#0070f3' }}>Meets</span> <span style={{ color: '#333' }}>Next.js</span>

        </h1>

        <div style={{ display: 'flex', justifyContent: 'center', gap: '40px', alignItems: 'center', marginBottom: '30px' }}>

          <Image

            src="/cloudflare-logo.svg"

            alt="Cloudflare Logo"

            width={150}

            height={75}

          />

          <Image

            src="/nextjs-logo.svg"

            alt="Next.js Logo"

            width={150}

            height={75}

          />

        </div>

        <p style={{ marginTop: '20px', fontSize: '18px', color: '#555' }}>

          A blazing fast, serverless web experience with <strong>OpenNext</strong> 🚀

        </p>

      </div>

    </div>

  );

}

This simple component serves as your landing page. It features a welcoming headline, an image, and a short description to indicate that the app is optimised with Cloudflare R2 (we will discuss later) for improved asset caching and load times.


Use SVG versions of the Next.js and Cloudflare logos (e.g., nextjs-logo.svg and cloudflare-logo.svg) and place them in the public/ directory. You can download them directly from their official brand resources, or check the shared GitHub repository if you're following along with the full example project.

Start your development server:

npm run dev

Then open http://localhost:3000 in your browser. You should see a polished and responsive homepage.

Add an API Route for Dynamic Content

To enable dynamic content fetching and showcase SSR (Server-Side Rendering), we will create an API route. This API route will serve as a mock dynamic data source for the application. The route will generate and return a JSON response containing a message and a timestamp each time it is requested.

In your project, navigate to the src/app/api/data/route.ts file. If the api/data folder structure does not exist, create it to organise the API route.

import { NextResponse } from 'next/server';

export async function GET() {

  const data = {

    message: 'Hello from the Cloudflare Workers API!',

    timestamp: new Date().toISOString(),

  };


  // Return the data as a JSON response with caching headers

  return NextResponse.json(data, {

    headers: {

      // Cache the data for 1 hour, and allow stale data while revalidating

      'Cache-Control': 's-maxage=3600, stale-while-revalidate',

    },

  });

}

Purpose:


This route handles a GET request and returns a JSON response containing a message and the current timestamp.

Caching: The response is cached using the Cache-Control header. The s-maxage=3600 indicates that the response should be cached for 1 hour. The stale-while-revalidate directive ensures that, even if the cached response becomes stale, it will still be served until a fresh one is fetched.

This caching strategy is useful for serverless functions or edge functions like Cloudflare Workers, ensuring high performance while keeping the data fresh.

In production, this API could fetch data from a database or an external API. However, for this demo, we are returning a static message and timestamp to demonstrate dynamic behaviour.


Test the API:


To see the result of your API route in action, run your Next.js app and visit:


http://localhost:3000/api/data

You should see a JSON response similar to the following



This endpoint can now be used by the frontend to fetch dynamic data for SSR. The data will be updated on every request, ensuring that the page rendered with Server-Side Rendering always uses fresh data.


Additional Notes:


The API route is essential for SSR (Server-Side Rendering). By fetching this dynamic data on each request, the page is rendered with the latest content each time.

Stale Caching is a powerful technique to minimise latency while still keeping data relatively fresh. This allows us to improve the performance of applications hosted on serverless or edge platforms like Cloudflare Workers.

Install the OpenNext Cloudflare Adapter

Now that you’ve set up your app and the API route, it’s time to integrate OpenNext with Cloudflare Workers to enable deployment. The OpenNext Cloudflare adapter will allow your Next.js app to run smoothly on Cloudflare Workers, handling SSR (Server-Side Rendering) and serving static assets directly from Cloudflare’s edge network.


To install the OpenNext Cloudflare adapter, run the following command in your terminal:


npm install --save-dev @opennextjs/cloudflare

Now that the adapter is installed, your app is ready to be deployed to Cloudflare Workers with the enhanced performance benefits of OpenNext.


Enabling Cloudflare R2 for Caching

Cloudflare R2 is a powerful, cost-effective object storage solution that’s ideal for caching data such as Next.js API responses. It offers a generous free tier that includes 10 GB of storage and 1 million read/write requests each month, which is more than enough for most small-to-medium applications. However, to activate R2 (even with the free tier), Cloudflare requires a credit card on file. Rest assured, you won’t be charged unless you exceed the free tier limits.


Steps to Enable R2:

Go to the Cloudflare Dashboard and log in with your Cloudflare account credentials.

Navigate to the Billing section, and then select Payment Methods.

Add a credit card to your account. This is a necessary step to use Cloudflare R2, even though you won’t be charged unless you exceed the free tier usage.

Once your payment method is added, go to the R2 section under Storage, click on Create Bucket.

Name the bucket nextjs-demo-cache (this name will match the configuration in the wrangler.toml file later).

Finally, click Create Bucket. This bucket will serve as the location for storing cached API responses.


Configuring for Cloudflare Workers

Now that you’ve set up Cloudflare R2, it’s time to integrate it with your Next.js app and configure the app to run on Cloudflare Workers. This involves using Wrangler, Cloudflare’s CLI tool, and updating the wrangler.toml configuration file.


Install Wrangler

Wrangler is Cloudflare’s command-line tool used to manage and deploy Cloudflare Workers. To install Wrangler globally, run the following command:


npm install -g wrangler

Authenticate with Cloudflare

Once installed, authenticate with your Cloudflare account:


wrangler login

This will open a browser window where you can log in to your Cloudflare account.



Update wrangler.toml

The wrangler.toml file is crucial for configuring your Cloudflare Worker project. Here’s an updated file for our app with explanations:


name = "nextjs-demo"

compatibility_date = "2024-09-23"

compatibility_flags = ["nodejs_compat"]


main = "./.open-next/worker.js"


[[r2_buckets]]

binding = "NEXT_INC_CACHE_R2_BUCKET"

bucket_name = "nextjs-demo-cache"

preview_bucket_name = "nextjs-demo-cache"


[assets]

directory = ".open-next/assets"

Breakdown of the wrangler.toml:

name: "nextjs-demo"This is a unique identifier for your Workers project. It will be used in the Cloudflare dashboard and as part of the project’s URL.

compatibility_date: "2024-09-23"Specifies the date to ensure that you’re using the latest Cloudflare Workers runtime features for compatibility with your app.

compatibility_flags: ["nodejs_compat"]Enables Node.js compatibility for Cloudflare Workers. This is necessary to support Next.js features like file system access and other server-side logic.

main: "./.open-next/worker.js"Points to the Worker script generated by OpenNext. This script contains the logic for your app, such as handling API routes, rendering, and interacting with R2.

R2 Bucket Configuration

[[r2_buckets]]: This section configures the integration with your R2 bucket for caching API responses.

binding: "NEXT_INC_CACHE_R2_BUCKET"This is the variable name used to reference the bucket within your Cloudflare Worker code.

bucket_name: "nextjs-demo-cache"This is the production bucket name that you created earlier in the Cloudflare dashboard.

preview_bucket_name: "nextjs-demo-cache"For simplicity, we use the same bucket for both production and development environments. This helps ensure consistency across environments.

Static Assets Configuration

[assets]: This section specifies where your static assets (such as images, CSS, etc.) are located. These assets are generated by OpenNext and served directly by Cloudflare Workers.

directory: ".open-next/assets"This tells Wrangler where to find the assets generated by OpenNext, which will be served by Workers.

Create open-next.config.ts for Cloudflare

In the project root, create the open-next.config.ts file to configure R2 caching for incremental static regeneration (ISR). This setup will store API responses in Cloudflare R2 (bucket nextjs-demo-cache).


Install Cloudflare Dependencies:

You should install the Cloudflare-specific dependencies for OpenNext instead of AWS-related packages.


Run the following to install the required Cloudflare adapter for OpenNext:


npm install --save-dev @opennextjs/cloudflare

Configure open-next.config.ts for Cloudflare:

In the project root, create the open-next.config.ts file to configure R2 caching for incremental static regeneration (ISR). This setup will store API responses in Cloudflare R2 (bucket nextjs-demo-cache).


import { defineCloudflareConfig } from '@opennextjs/cloudflare';

import r2IncrementalCache from '@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache';


export default defineCloudflareConfig({

  incrementalCache: r2IncrementalCache,

});

Update package.json Scripts

Next, update the package.json to include scripts for development, build, and deployment using Cloudflare Workers.


package.json:

"scripts": {

  "dev": "next dev",

  "build": "next build",

  "start": "next start",

  "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",

  "deploy": "opennextjs-cloudflare build && opennextjs-cloudflare deploy"

}

Explanation:


preview: Tests the application locally in the Cloudflare Workers runtime.

deploy: Builds and deploys the app to Cloudflare Workers.

Build and Deploy

To initiate the build and deployment process, execute the following command in your terminal:


npm run deploy

This command does several things:


Build: The app will be built using next build, which prepares all the static assets and prepares the app for deployment.

Package: The app is then packaged using OpenNext, a tool that integrates with Cloudflare Workers. This process will ensure that both your R2 cache and static assets are bundled and ready for deployment.

Deploy: Finally, the app will be deployed to Cloudflare Workers. The deployment is directed to the URL: https://nextjs-demo.<your-account>.workers.dev



You will be able to access your deployed app at this URL. It’s the live instance where your app will run with Cloudflare’s edge performance, ensuring low latency and fast access.

Test the Deployment:

After the deployment is successful, perform the following checks to ensure that everything is functioning as expected:


Test the Homepage: Navigate to the homepage of your app. This ensures that the Next.js app is properly deployed and the front-end is functioning.


Test the /api/data Endpoint: The API route (/api/data) should return a JSON response with a timestamp and a message. Verify that the response is cached and served by Cloudflare’s R2 storage. You can inspect the headers of the response to check whether it is being cached. Cloudflare Workers, together with R2, allow caching of responses to improve performance, especially for frequently requested data.


If you’ve reached this step, it means you have successfully deployed your Next.js app to Cloudflare Workers, leveraging R2 caching and edge delivery for your static assets. Your app is now live with enhanced performance, scalability, and speed, powered by Cloudflare’s infrastructure.


Limitations and Challenges

While this approach has significantly improved cost efficiency and deployment flexibility for my projects, there are some important considerations to keep in mind:


OpenNext Is Still in Development: The OpenNext project is evolving rapidly, but it is not yet as stable or feature-complete as Vercel’s native Next.js support. Developers may encounter occasional bugs, incomplete features, or additional configuration requirements, particularly around advanced rendering strategies like ISR and middleware.

Cloudflare Workers Execution Constraints: Cloudflare Workers come with hard limits on CPU time and memory allocation. These constraints are generally manageable for standard web applications, but they can become bottlenecks in more complex or computationally intensive scenarios. For applications that rely heavily on server-side processing or large data payloads, these limits may present architectural challenges.

Next.js Feature Compatibility: Some advanced features in Next.js may not work out of the box and require additional setup. This includes aspects like image optimisation, dynamic routing in the app/ directory, and some server-side rendering behaviours. While most core functionality is supported, the developer experience may not be as seamless as on Vercel.

Limited Debugging and Observability: Unlike Vercel’s integrated developer tools, the debugging experience with Workers is more fragmented. Observability requires manual setup via Wrangler CLI or third-party logging tools. There is less real-time visibility into deployments, making troubleshooting slightly more involved.

Performance Limitations for Data-Intensive Applications: The most significant challenge I’ve encountered relates to applications that perform heavy server-side data processing. Due to the execution limits of Workers, these applications may require architectural adjustments or offloading computation to other services. For typical content-driven applications or APIs with lighter logic, this has not been a problem.

Final Thoughts

For teams evaluating alternatives to Vercel, especially those concerned with cost, platform lock-in, or infrastructure control, Cloudflare Workers combined with OpenNext and R2 presents a compelling option. While the ecosystem still has areas to mature, the stack offers strong global performance, lower operational costs, and the flexibility to scale without committing to a proprietary platform.


Start with a smaller project to evaluate how the tooling fits your workflow. As OpenNext continues to evolve, the deployment experience and compatibility will only improve.


Refer to this GitHub repository for code samples and configuration files used throughout this setup: https://github.com/prateekjaindev/nextjs-cloudflare


You can follow me on X (@PrateekJainDev) and LinkedIn (in/prateekjaindev) for more updates!


Thanks for reading, and Happy coding! 🚀



156



2



Programming

Technology

DevOps

Development

Cloud Computing

156



2





Prateek Jain


Follow

Written by Prateek Jain

877 followers

·

15 following

DevSecOps Architect at Tech Alchemy


Responses (2)

To respond to this story,

get the free Medium app.


Open in app

Alae Meftah 🦋

Alae Meftah 🦋


she

1 day ago



+ 50 claps

1


Alae Meftah 🦋

Alae Meftah 🦋


she

1 day ago



I Switched from Vercel to Cloudflare for Next.js


Wow great

More from Prateek Jain

Docker Image Optimisation

Prateek Jain

Prateek Jain


A Step-by-Step Guide to Docker Image Optimisation: Reduce Size by Over 95%

When I first deployed a Node.js app to production using Docker, I was stunned, The final image size was a whopping 1.2 GB. All for just a…


May 4

805

20



Why I Replaced NGINX with Traefik in My Docker Compose Setup

Prateek Jain

Prateek Jain


Why I Replaced NGINX with Traefik in My Docker Compose Setup

For a long time, NGINX was my go-to solution for setting up reverse proxies. Whether it was a Node.js app, a Django backend, or even a…


Apr 19

807

13



Build a Scalable Log Pipeline on AWS with ECS, FireLens, and Grafana Loki: Part 1

Prateek Jain

Prateek Jain


Build a Scalable Log Pipeline on AWS with ECS, FireLens, and Grafana Loki: Part 1

If you’re running containerised workloads on AWS ECS Fargate, setting up a proper logging solution isn’t just a nice-to-have, it’s a must…


Apr 1

46

4



Vibe Coding with Amazon Q Developer CLI

Prateek Jain

Prateek Jain


Vibe Coding with Amazon Q Developer CLI

Recently, I’ve been exploring several AI-powered IDEs and terminal apps designed to simplify the coding and deployment processes. While…


Apr 26

68

1



See all from Prateek Jain

Recommended from Medium

12 Open-Source Full Stack JavaScript Projects (You’ll Regret Ignoring)

Let’s Code Future

In


Let’s Code Future


by


Ask With Ai


12 Open-Source Full Stack JavaScript Projects (You’ll Regret Ignoring)

Please don’t ignore these like I did at first. You’ll regret it later. Trust me.


Apr 28

276

4



Docker Image Optimisation

Prateek Jain

Prateek Jain


A Step-by-Step Guide to Docker Image Optimisation: Reduce Size by Over 95%

When I first deployed a Node.js app to production using Docker, I was stunned, The final image size was a whopping 1.2 GB. All for just a…


May 4

805

20



performance improvement

Sachin Kasana

Sachin Kasana


⚡ From 5s to 500ms: How I Reduced My Page Load with Real Metrics

Every second of delay means a drop in engagement, conversions, and search ranking.

Apr 8

2



10 Powerful Next.js Optimization Tips

Tajammal Maqbool

Tajammal Maqbool


10 Powerful Next.js Optimization Tips

Do you have NextJS Project but not optimized or not know how to do it? Don’t worry. Optimizing your Next.js app can improve speed…

May 3

58



Why You Should Build an MCP Server This Weekend

Coding Nexus

In


Coding Nexus


by


Code Pulse


Why You Should Build an MCP Server This Weekend

I haven’t seen an opportunity this wide open since the early days of mobile apps.


May 4

1.6K

26



PHP vs. Node.js in 2025: The Startling Performance Reality

Cloud Guru

Cloud Guru


PHP vs. Node.js in 2025: The Startling Performance Reality

For years, the web dev world has been locked in a showdown: PHP vs. Node.js. Each side has its die-hard fans, endless Reddit threads, and…


Apr 26

43

15



See more recommendations

Post a Comment

0 Comments

Close Menu