In this blog post, I’m going to walk you through deploying Next.js 13 (app dir) to Cloudflare Pages. And don't worry, I won't skip any details, but will do my best to explain everything as clearly as possible.
So, let's roll up our sleeves and get started.
Step 1: Creating a Next.js application
So, let's start by creating a new Next.js application. You're smart, you know the drill - just give this command a run.
You can follow my lead and say a big, fat 'YES' to the following options. (just kidding, you can choose whatever you want, just make sure to select the app dir
option when asked as that is what we're going to use in this blog post)
Now, change the directory to your website's directory using cd my-website
and run npm run dev
to start the development server. It's live on localhost:3000!
Step 2: Setting up a GitHub repository
Now that we have our Next.js app up and running, let's set up a Github repository for it. I have created a new repository on Github called nextjs-cloudflare-example
and will then set it as my remote origin and push the code to it.
That's it, now you should be able to see your code on Github.
Step 3: The Cloudflare Pages Configuration
To deploy to Cloudflare Pages, we need to use the @cloudflare/next-on-pages
package to ensure the app is compatible with Cloudflare Pages. You need to add a new build step to package.json and add the package to dependencies. I will call this step pages:build
and will use npx
to run the build command.
Your package.json
should look similar to this:
p.s. make sure to remove the comments from the code below, as JSON doesn't support comments and it will break your build.
Step 4: Configuring the Runtime
You see, by default, Next.js runs on two different runtimes: Node.js and edge (V8 Isolates). When you deploy on Vercel, they use a combination of AWS Lambdas for the Node.js runtime and Cloudflare Workers for the edge runtime. However, Cloudflare Pages only supports the edge runtime. So, we need to configure our app to run on the edge runtime.
To do that, we need to add the runtime route segment configuration to our root layout.tsx
file so that it gets applied to all pages. This is what my layout.tsx
file looks like:
Once you've done that, you can commit and push your code to Github. Now, we're ready to deploy to Cloudflare Pages.
Step 5: Deploying to Cloudflare Pages
Head to Cloudflare pages, log in and click on 'Create application'. Switch the tab to pages
and click on Connect to Git
. Select your repository and branch and click on Begin setup
.
Now, select Next.js
as the framework and change the build command to npm run pages:build
. Keep the output directory as .vercel/output/static
and click on Save and Deploy
.
Now, sit back, grab some popcorn, and watch your app get deployed. Don’t freak out when the first deployment fails; it’s just all part of the process™. We will fix it in the next step.
Step 6: Configuring the Compatibility Flags
Head to Settings > Functions > Compatibility Flags and add the nodejs_compat
flag to both the production and preview environments. Make sure the compatibility date
for both environments is set to at least '2022-10-30'.
Now go back to the Deployments
tab and click on Redeploy
to deploy your app again. This time, it should deploy successfully, and you will find your app running on the provided URL - a big hurray on successful deployment to Cloudflare pages!
Step 7: Making it Truly Static
If you refresh your app a few times and then head to your function usage on Cloudflare Pages, you will be greeted with a surprise. The function usage is increasing with every refresh. This is because Next.js defaults to SSR and Streaming
mode, even for fully static pages unless you go out of your way to opt-out of SSR and Streaming.
See that little ℇ
next to the route? That means it's using SSR and Streaming mode. Why? I have no idea. 🤷🏻♂️
Anyway, to fix this, head back to layout.tsx
and modify it to:
This will force Next.js to use the static mode for all pages. Now, do the boring git commit and push dance and redeploy your app.
So, we should be good now, right? Well, not quite. If you check out the build logs, you will still see that snarky little ℇ
next to the route. Why? Because of this:
Don't yell at me, I didn't make the rules. Apparently, we cannot use the force-static
mode with the edge runtime. So, we need to remove the runtime config to make this work. So, you can either remove the runtime export from layout.tsx
or set it to nodejs
like so:
Now, let's redeploy and pray to the CI/CD gods that it works this time.
Step 8: Dealing with Dynamic Routes
Alright, so another issue that I ran into was that dynamically created static pages were still being served using SSR and Streaming mode. Let me show you:
Let's say we have a dynamic route called blog/[slug]/page.tsx
and we want to statically generate the pages for all the blog posts. So, we would do something like this:
I will not go into details of how this page should be implemented, but for the sake of this example, imagine we get the slug for each blog post, and in the component we load the actual blog post from the CMS or whatever and then render it. But for now, I am just going to render the slug.
Now let's define the generateStaticParams
so we can statically generate the pages for all the blog posts during build time.
Here, we are just returning an array of slugs and then mapping over them to return an array of objects with the params
key. This is telling Next.js to statically generate the pages for all the slugs during build time.
We have given Next.js all the slugs that we want to statically generate during build time, we have defined the generateStaticParams
function and we have defined the dynamic
export to force static mode. So, we should be good to go, right? Let's deploy and see what happens.
Surprise, surprise! We got an error and the build failed. Why? Because even though we are asking Next.js to statically generate the pages, it is defaulting to SSR and Streaming mode for the fallback route. So, we need to explicitly tell Next.js that we don't have a fallback route for our STATICALLY GENERATED pages. So, let's do that.
Now, let's redeploy our changes and once again pray to the CI/CD lords to save us from further embarrassment.
As you can see, we got rid of the error and that little ℇ
next to the route. Now, if you head to the blog post page, you will see that it is being served statically.
Step 9: Adding a Custom Domain
Finally, now that we have our app deployed to Cloudflare Pages, let's add a custom domain to it. Head to the Custom domains
tab and click on Set up a custom domain
.
Here you can either add a subdomain or a root domain, it will then give you a list of DNS records that you need to add to your DNS provider. So, head to your DNS provider and add the records.
If you are using Cloudflare as your DNS provider, you can just click on Verify DNS configuration
and it will automatically add the records for you.
Wrapping Up
And that's it! We have successfully deployed our Next.js app to Cloudflare Pages. From here on, Cloudflare will automatically deploy your app to production anytime you push to the main branch, and it will also create a preview deployment for the rest of the branches.
In addition, Cloudflare Pages gives you a lot of flexibility and control over your deployments, in addition to a ton of features, and benefits such as zero bandwidth costs, free SSL, and more. So, I highly recommend you check it out.
Interested in LogSnag?