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?
