Fullstack application using Cloudflare Pages

11 months ago

Fullstack application using Cloudflare Pages

I migrated most of my services to Cloudflare Pages, and in doing so, I got rid of Next.js and simply used Cloudflare Pages' functions implementation with my own low-level implementations.

Cloudflare Pages support pretty much every framework that you can think of and that is great, if you're simply looking for a guide on how to deploy a fullstack application to Cloudflare Pages, I advise you to first head over to their framework guides.

This article follows the process I took to create a static website using server rendered React DOM elements in TypeScript. I wanted to make my blog as simple as possible and I wanted the simplest low-level framework to do this in.

Considering the folder structure

One of the first questions to answer was how I wanted the folder structure to be. If we consider the base layout for Cloudflare Pages, this is how an ideal runtime build would look like:

├── functions/ │ ├── _middleware.js │ └── articles/ │ └── [articleId].js ├── styles/ │ └── style.css ├── scripts/ │ └── app.js ├── index.html └── favicon.ico

So, what I had to do, is set up a folder structure that easily tells me as a developer, whether I'm in a client or server context (for example, if I'm working on a client component or a server component), so I begun with just that.

└── src/ ├── client/ └── server/

In these two folders, I set up one TypeScript config each, as one will be targeting an actual browser and the other will be targeting Cloudflare Pages functions.

Starting with the server, I put the output directory to be the very base path of my repository, this is because as of this time, Cloudflare doesn't allow us to change the functions folder path, it must be in the very root of the repository, regardless of the build directory specified on the dashboard.

See the TypeScript guide on Cloudflare Docs for more information on configuring TypeScript.

{ "exclude": ["../../node_modules"], "compilerOptions": { "jsx": "react", "target": "esnext", "module": "esnext", "moduleResolution": "node", "outDir": "../../", "allowSyntheticDefaultImports": true, "lib": [ "esnext" ], "types": [ "@cloudflare/workers-types" ] } }

With this done, I untracked all of the folders in my server project, from the repository root and created a small script to delete those folders on command. I also included a .build folder in the deletion script as that will come in the client setup.

rmdir /s /q .build functions components controllers models

After that, this is how I decided my project structure to be, although it's entirely up to you what you need or how you want to structure it. The only important part is that the functions folder, must always be called functions, or you must otherwise rename it when you build your project.

└── src/ ├── client/ └── tsconfig.json └── server/ ├── components/ ├── controllers/ ├── functions/ ├── models/ ├── global.d.ts └── tsconfig.json

Handling routing

What I really like about Cloudflare Pages functions, is that they support routing right away, without the need for a third party framework. Essentially, routing was nothing less than just adding a parameter in a square bracket notation, e.g.:

└── src/ ├── client/ └── tsconfig.json └── server/ ├── functions/ │ ├── index.tsx │ └── articles/[articleId].tsx ├── global.d.ts └── tsconfig.json

Integrating serverside React DOM rendering

Given the react-dom package, rendering React is no harder than using:

ReactDOMServer.renderToString(element);

All there is to do when returning a response with the string, is setting the Content-Type header to "text/html", aaand I still made a package for it... it felt too meta to be in each of my projects I'll be using with this template (so far it's used in 2 projects).

With the wrapper package, it's as simple as:

export function onRequest(context) { return ReactDOMResponse(

Hello from React!

); };

And that's basically it... once compiled, this will render the React element on the server and print... "Hello from React!" in a large header.

I don't feel restricted by any frameworks that make web development feel like rocket science, it's so simple to work with Cloudflare Pages functions.

Integrating Cloudflare D1

Important note from April 7th, 2023

Cloudflare D1 is in open alpha, and is not recommended for production data and traffic.

If you decide to use D1, you should make your own determination whether it's suitable for your project or not. In most cases, you should hold off on using D1 in production.

In my case, considering the very limited amount of data I store and access on D1, I'm okay with the risks that it brings to the availability of my own developer blog.

Integrating D1 is no different than following the D1 bindings guide. For example:

export async function onRequest(context) { const { articleId } = context.params; const article = await context.env.DATABASE.prepare("SELECT * FROM articles WHERE id = ?").bind(articleId).first(); if(!article) return Response.redirect("/404.html"); return ReactDOMResponse(

{article.title}

{article.body}

); };

Ready-to-use template

Cloudflare Pages React Example Open page in a new tab

Following this article, I've published the template for use that you can use as your boilerplate template for TypeScript with React on Cloudflare Pages.