Cloudflare Workers - SPA with Vuejs

Hello I have deployed my Vue.js app to Cloudflare workers using the following commands:

wrangler generate --site
wrangler publish --env dev

This is my wrangler.toml:

account_id = "xxx"
name = "name"
type = "webpack"
workers_dev = true

[site]
bucket = "./dist"
entry-point = "workers-site"

[env.dev]
name = "name"
route = "xxx.com/*"
zone_id = "XXX"
account_id = "XXX"

The website is fine and live on “xxx.com” but when I refresh the page on any other route I get this error message:

could not find es-es/index.html in your content namespace
could not find category/65/index.html in your content namespace

On nginx I had to create a .htaccess, but I have no idea on how to make it work here.

This is my index.js in case it helps:

import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'

/**
 * The DEBUG flag will do two things that help during development:
 * 1. we will skip caching on the edge, which makes it easier to
 *    debug.
 * 2. we will return an error message on exception in your Response rather
 *    than the default 404.html page.
 */
const DEBUG = false

addEventListener('fetch', event => {
  try {
    event.respondWith(handleEvent(event))
  } catch (e) {
    if (DEBUG) {
      return event.respondWith(
        new Response(e.message || e.toString(), {
          status: 500,
        }),
      )
    }
    event.respondWith(new Response('Internal Error', { status: 500 }))
  }
})

async function handleEvent(event) {
  const url = new URL(event.request.url)
  let options = {}

  /**
   * You can add custom logic to how we fetch your assets
   * by configuring the function `mapRequestToAsset`
   */
  // options.mapRequestToAsset = handlePrefix(/^\/docs/)

  try {
    if (DEBUG) {
      // customize caching
      options.cacheControl = {
        bypassCache: true,
      }
    }
    return await getAssetFromKV(event, options)
  } catch (e) {
    // if an error is thrown try to serve the asset at 404.html
    if (!DEBUG) {
      try {
        let notFoundResponse = await getAssetFromKV(event, {
          mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
        })

        return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
      } catch (e) {}
    }

    return new Response(e.message || e.toString(), { status: 500 })
  }
}

/**
 * Here's one example of how to modify a request to
 * remove a specific prefix, in this case `/docs` from
 * the url. This can be useful if you are deploying to a
 * route on a zone, or if you only want your static content
 * to exist at a specific path.
 */
function handlePrefix(prefix) {
  return request => {
    // compute the default (e.g. / -> index.html)
    let defaultAssetKey = mapRequestToAsset(request)
    let url = new URL(defaultAssetKey.url)

    // strip the prefix from the path for lookup
    url.pathname = url.pathname.replace(prefix, '/')

    // inherit all other props from the default request
    return new Request(url.toString(), defaultAssetKey)
  }
}

Hello @marcosa!

This error is occurring because Workers Sites, by default, assumes that you’re using a “static” site – meaning that when you make a request to xxx.com/category/65, it is looking for the presence of category/65/index.html in the content of your site that has been uploaded to KV.

With SPAs, you need to customize the options.mapRequestToAsset function in your Workers code to always return /index.html, because the SPA will handle all the routing for you. In handleEvent, you can uncomment out the line options.mapRequestToAsset = handlePrefix(/^\/docs/), and replace it with:

options.mapRequestToAsset = request => {
  const url = new URL(request.url)
  url.pathname = `/`
  return mapRequestToAsset(new Request(url, request))
}

Admittedly, this is something we need to cover in our docs (the relevant issue is here), so if you’d like to test this, I’d love to know if it works! I’ve run it locally w/ one of my projects and it seems to handle the routing like I’d expect. Hope it helps!

Edit: Ah, some people on my team have checked over this code and rightly pointed out that this might be a bit too simple – it’ll return / for any CSS/JS/etc assets that you would want to load on your site, as well. Turns out this is a tricky problem :sweat_smile: hope to have a better solution here soon, we’re going to think on some potential solutions here. Thanks for raising this issue!

3 Likes

Thanks. I hope you find a solution :pray:. It would be awesome if this can be done. I think a lot of people would benefit of hosting simple vue js applications con Cloudflare Workers.

1 Like

Absolutely! It’s a super important use-case – I work on a ton of SPA apps too, so I’m very motivated to find a fix for this. Thanks @marcosa!

3 Likes

FWIW I gave an answer (in fact, two different answers) on Stack Overflow that should work here:

1 Like

Thank you so much. It’s working now.

Thanks @kentonvarda, that’s a great solution!