Dev log: Cloudflare Pages w/ Astro
Recently I have been using Cloudflare Workers extensively at work (designed a whole architecture centered around them). This is how I became interested in trying out Pages, too.
A good candidate for this test is my personal blog. I am hoping to gain a flexible environment to quickly prototype and host some simple applications, too.
This is my play-by-play dev log of getting started with Cloudflare Pages.
Contents
First steps
I like to start from the developer docs.
Opening the site, I first check out the Get started section. The guide talks about three methods of deploying Pages.
The first method is connecting a Git provider (either GitHub or GitLab) directly. The second method is using their CLI called C3
(i.e. create-cloudflare-cli
) and the third method is named Direct Upload, which uses either a Drag and drop interface (in their dashboard) or their CLI called wrangler
.
Thinking about the three options, it seems that C3
and wrangler
end up in the same flow. C3
is just a convenience tool to set you up with a template for the project, which can later be deployed using one of the other methods (wrangler
or connecting a git provider).
Sidetrack: Choosing the framework
One difficult choice arises at this point: should I use a framework or go with something vanilla?
Writing a personal blog in HTML could be fun, but get a bit tedious over time just for the markup overhead. A rather straightforward option would be to write markdown files for the blog, while also retaining the option to add something more complex to the site later.
Lately, I’ve been curious to try out Hugo and Astro.
Hugo vs Astro
Looking into the docs, Hugo has some really attractive features alongside a large community and an established track record. A lot of available themes is a nice plus!
However, glancing through things superficially, it seems due to its feature-richness, the learning curve might also be relatively steep (especially, if one seeks to become proficient with it). Kudos for an amazingly succint Quick start, though!
On the other hand, Astro is a newer kid on the block. They also support i18n
through routing, have some nice starter themes and have some interesting approaches in regards to how JS is delivered to the client.
Recap the requirements
Perhaps this is a good point to remind oneself, what one actually wants to achieve.
Let’s write it down:
- Effortless to write: preferrably markdown, but not limited to that
- Easy to maintain
- Supports
i18n
- Fun to experiment with
- Does it need to be statically generated? Not really, though that’s nice too.
Both of the frameworks check all these boxes.
I do a couple of searches:
For a static site generator (SSG) like Hugo, how does one usually work with dynamic data?
Since I am not too concerned with SEO, using JavaScript to fetch stuff is no issue. Otherwise, having dynamic data always available might require triggered site generation.
Not really highly relevant, but since it popped into my head: what about diagrams using Mermaid?
Turns out both have support for it through templating/plugins.
Conclusion
Though somewhat arbitrary, this time I’ll try Astro, since the tooling is also close to what I have worked with a lot lately.
Cloudflare Pages with Astro
Back to Cloudflare Pages’ docs.
They have a framework guide available for Astro, which will come in handy. Nice!
Set up
I use their C3
tool to create a new Astro project.
pnpm create cloudflare@latest my-astro-app -- --framework=astro
This command does not work as I hope. I get an error with an incompatible node version. I install the latest node LTS and try again, but still it fails. Weird…
Okay, after a little bit of troubleshooting, seems my pnpm install was out of date. Fixed with volta install pnpm
.
The full, guided installation flow is actually pretty nice. It ran as follows:
using create-cloudflare version 2.9.3 ╭ Create an application with Cloudflare Step 1 of 3 │ ├ In which directory do you want to create your application? │ dir ./astro │ ├ What type of application do you want to create? │ type Website or web app │ ├ Which development framework do you want to use? │ framework Astro │ ╰ Continue with Astro via `npx create-astro\@4.7.1 astro --no-install` astro Launch sequence initiated. ◼ dir Using astro as project directory tmpl How would you like to start your new project? Use blog template ◼ No problem! Remember to install dependencies after setup. ts Do you plan to write TypeScript? Yes use How strict should TypeScript be? Strictest git Initialize a new git repository? No ◼ Sounds good! You can always run git init manually. ✔ Project initialized! ■ Template copied ■ TypeScript customized next Liftoff confirmed. Explore your project! Enter your project directory using cd ./astro Run npm run dev to start the dev server. CTRL+C to stop. Add frameworks like react or tailwind using astro add. Stuck? Join us at https://astro.build/chat ╭─────╮ Houston: │ ◠ ◡ ◠ Good luck out there, astronaut! 🚀 ╰─────╯ ╭ Configuring your application for Cloudflare Step 2 of 3 │ ├ Installing dependencies │ installed via `npm install` │ ├ Installing adapter │ installed via `npx astro add cloudflare` │ ├ Installing wrangler A command line tool for building Cloudflare Workers │ installed via `npm install wrangler --save-dev` │ ├ Retrieving current workerd compatibility date │ compatibility date 2024-01-17 │ ├ Adding command scripts for development and deployment │ added commands to `package.json` │ ╰ Application configured ╭ Deploy with Cloudflare Step 3 of 3 │ ├ Do you want to deploy your application? │ no deploy via `npm run pages\:deploy` │ ├ APPLICATION CREATED Deploy your application with npm run pages\:deploy │ │ Navigate to the new directory cd astro │ Run the development server npm run pages\:dev │ Deploy your application npm run pages\:deploy │ Read the documentation https://developers.cloudflare.com/pages │ Stuck? Join us at https://discord.gg/cloudflaredev │ ╰ See you again soon!
The installer prompts whether one wants to deploy the app. I choose not to deploy right away, because I want to first run it on local to understand what’s what.
Exploring the codebase
I run pnpm dev
and the dev server starts without a hitch and relatively fast (looks like they use vite
under the hood). Navigating to localhost:4321
shows me the default Astro blog template. A pretty nice and fast way to get started.
Interstingly, just throwing this markdown file itself under src/pages/blog
mostly works. However, it seems that it’s rendered as a page (not as content/blog post). Furthermore, it would seem to expect some kind of a template juding by the fact, that it’s completely unstyled as opposed to other default pages.
I look at the directory structure and notice that there’s also a src/content
. This is probably the right place.
Throwing this md
file under there hot-reloads the dev server (nice), but gives me an error about a mismatch in regards to the expected collection scheme
. This seems great! (Maybe I should read the docs at this point… ‘:D)
Fixed this by adding an Astro header (not sure what the official term is, but found it by looking at the example posts) to the top of the markdown file.
Now the blog post appears in the “blog” view. However, clicking on a post gives me another error: post.render is not a function
.
Not sure about the exact reason yet, but it seems that if a given post in the collection doesn’t match the page slug (basically the given URL path on that route), then the render
function is not available. Need to look under the hood on this one.
(… a little time passes …)
So, seems that the problem is that for whatever reason, in the default template, a prerender directive is simply not set.
Adding export const prerender = true;
to src/pages/blog/[...slug].astro
solves the issue. This will create static HTML files during the build process.
(… a little more time passes …)
Ok, this known to happen: I actually spent the last hour or so looking deeper into Astro, though I meant to focus on Cloudflare Pages.
Let’s get back to Cloudflare Pages.
Deploying
So now I have a default Astro blog template and added this post itself under src/content/blog
and it all works.
The next step is to simply run pnpm pages:deploy
as defined in ./package.json
.
This is basically short for astro build && wrangler deploy ./dist
. As one might guess, Astro will make a build, which will then be deployed from the ./dist
directory to Cloudflare Pages.
A few noteworthy moments in this automated process are:
- I am using the
@astrojs/cloudflare
adapter that came out of the box with the installation.- This supports both server-side rendering (SSR) and static site generation (SSG) out of the box and makes things convenient.
- I changed the output mode for Astro to be
hybrid
, which essentially means a hybrid of server-side rendering (SSR) and statically generated sites. I chose this, because mostly my pages should be statically generated, but I also plan on including some dynamic parts going forward. - You can find more about this in Astro docs
- After the build is complete, the user is prompted for
- a new for the project (for Cloudflare Pages),
- what git branch the deployment should be linked to and
- whether or not to opt-in to
Wrangler
(Cloudflare) telemetrics.
After the initial Cloudflare Pages’ settings are done, Wrangler goes ahead and compiles (transpiles) the Worker code into a neat bundle, which is uploaded to Cloudflare.
And just like that, we are given a *.pages.dev
style URL and the deployment is live.
Wrap up
After the successful deploment, I go to the Cloudflare dashboard and navigate to the Workers & Pages
section.
Dashboard chores
The dashboard for the Pages’ project (as named above) shows a bunch on interesting options, amongst others branch-based deployments. I consider this convenient, because it enables ephemeral environments (AFAIK, this is also what powers Vercel.)
I want to now set up a custom domain for the Pages’ deployment. At least a glance through the docs gives us only talk of a GUI flow. I do find myself wishing that the custom domain could be set up using wrangler.toml
as it is for Workers.
I yield and set up this littlebitof.xyz domain to route to the Pages’ deployment as prescribed. Further, out of curiosity, I also enable Web analytics that Cloudflare offers out of the box.
Finally, I set up a redirect rule to forward all www.*
requests to the apex domain.
Conclusion
Previously, I have rather enjoyed developing with Workers, so I was not surprised that Cloudflare Pages also provide a fairly smooth and seamless experience. I think I will recommend this going forward.
On another note, I am looking forward to having a more unified experience between these two products (i.e. Pages functions & Workers), and judging by their development activities, the Cloudflare team is working on doing just that.
It will be exciting to explore the possibilities further of what all can one do with their serverless products (especially once they work together more seamlessly).
Alas, that will be a story for another time.