Setting Up Cloudflare Functions for an Astro Project?

Hello everyone!

Deployed my site with Pages last week and now I am just adding one or two finishing touches before leaving it alone for good. I have been using Cloudflare Pages for deployments and it has served me well, thankfully. All smooth sailing.

Currently, I am working on adding a page view counter to each blog post. I was initially following [this]( community recipe, but it became obvious to me that its actually quite outdated and was confusing me more than helping me.

As of right now, here is what I have:

  1. BlogViewCount React component that only loads on client side. The core functionality looks like the following:
useEffect(() => {
    const path = encodeURIComponent(window.location.pathname);
    const addView = async () => {
      fetch('/api/pageViews', {
        method: 'PUT',
        referrerPolicy: 'same-origin',
        body: JSON.stringify({
        headers: {
          'Content-Type': 'application/json',

    const fetchViews = async () => {
      try {
        const response = await fetch(`/api/pageViews?path=${path}`);

        if (!response.ok) throw new Error('Network response was not ok');

        const data = await response.json();
      } catch (error) {

  }, []);
  1. The endpoints in the {root}/functions/api/pageViews.ts file (pretty much directly from the link above):
export const prerender = false;

// PUT /api/pageViews
export const onRequestPut: PagesFunction<{ PAGE_VIEWS: KVNamespace }> = async (
) => {
  const {
    env: { PAGE_VIEWS },
  } = context;

  const { path } = await request.json() as any;

  let currentCount = Number(await PAGE_VIEWS.get(path));
  if (!currentCount || isNaN(currentCount)) {
    currentCount = 0;

  await PAGE_VIEWS.put(path, String(currentCount + 1));

  return new Response(null, {
    status: 204,
    statusText: 'ok',

// GET /api/pageViews
export const onRequestGet: PagesFunction<{ PAGE_VIEWS: KVNamespace }> = async (
) => {
  const {
    env: { PAGE_VIEWS },
  } = context;

  const path = new URL(request.url).searchParams.get('path');

  if (!path) {
    return new Response(null, {
      status: 404,

  const encodedPath = encodeURIComponent(path);
  const count = (await PAGE_VIEWS.get(encodedPath)) ?? 0;

  return new Response(JSON.stringify({ count }), {
    status: 200,
  1. What I believe to be the correct astro config:
  output: 'hybrid',
  adapter: cloudflare({ mode: 'directory' }),
  1. And lastly, I run with the following command (postbuild is just for pagefind):
astro build && npm run postbuild && npx wrangler pages dev ./dist

Given all the above, when I run the project, I see the [[page]].js file generated in {root}/functions/ but nothing else. The page itself which loads the page view component and make the fetch calls gets nothing but 404s and 405s. It seems like the server just isn’t running the Cloudflare functions at all.

If I deploy, I do see that in the Routing configuration populating with the routes I have defined, as expected, under the Functions tab for that deployment. However, the front end isn’t reaching these endpoints when it makes these requests, and instead is just attempting request off the server generating the front end view.

Am I missing a step here? Thanks!

Hello saidk,

The @astrojs/cloudflare adapter uses the advanced mode by default, which disables the function directory.

I recommend using Astro Endpoints instead:

Another option would be not to use @astrojs/cloudflare and build a static output instead. Then the functions directory should work.

KV is not really suited for view counters as it only supports 1 write per second to the same key and it can take up to 1 minute for writes to take effect everywhere. This will lead to the view count in KV being much lower than the actual view count. Maybe Durable Objects is a better fit.

Hope that helps!

Hello @leander.gilles ! Thanks for the response. I actually ended up moving to Astro Endpoints, and it was great to see that the astrojs/cloudflare adapter actually deployed those endpoints as Cloudflare Functions as well.

I knew about the read limitations (not a big deal, since view count doesn’t need to be accurate for every single page served), but I did not know about the 1 write per second. This won’t be a problem for now, since my site likely wont be getting that sort of traffic, but if it ever does get that kind of traffic, I’ll be sure to move over to Durable Objects.

Thanks for the suggestion and for your time!

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.