Caddy V2 - single page app help/example? Rewrites are being weird

1. Caddy version (caddy version):


2. How I run Caddy:

Docker image

a. System environment:

Dev system Win 10, final deployment on Ubuntu

b. Command:

docker build . 
docker run -p 80:80

(note that this is on test system)

c. Service/unit/compose file:


FROM caddy:2.0.0

COPY Caddyfile /etc/caddy/Caddyfile
COPY dist /var/html/www

d. My complete Caddyfile or JSON config:

:80 {
    root * /var/html/www
    encode gzip

    @work {
        path /work/*

    @app {
        path /work/app.min.js
    @best {
        path /work/test.js
    @fonts {
        path /work/fonts.css

    rewrite @app /app.min.js
    rewrite @work index.html
    rewrite @fonts /fonts.css
    rewrite @best /test.js



3. The problem I’m having:

I’m currently building what is intended to be a single page site using the History api using Docker. It’s just a frontend site, no backend stack involved but I need to rewrite some paths and I’m having a bit of trouble.

For those unfamiliar, essentially the goal is

  • All paths should load / under the hood while keeping the url intact
  • Static files paths should still work as expected rewrite or no rewrite

After a lot of trial and error the above Caddyfile seems to have gotten me most of the way there, but I am running into the most bizarre issue at the moment.

When you navigate to / , everything loads and functions as expected. However, on any other path, while as far as I can tell, things load correctly as well, my javascript doesn’t fire. It looks like the correct HTML is there, the site javascript appears to have loaded as well, things just don’t start for whatever reason.

Not sure if it will help or not, but I have noticed some other oddities as well.

  • Adding a second Javascript file seems to break things too(reflected above with test.js). The browser is somehow returning the the home page html instead of the javascript in spite of the same rewrite being done like on the @app route. The main Javascript file referenced by the @app route is being interpreted correctly as Javascript. Both test.js and app.min.js are at the root of the site folder.

  • My main browser is Firefox and I noticed in devtools, network tab, under the “initiator” column, the value listed is “browsing-content.js:” instead of “document” like when on the home page.

To clarify - I do not intend to use the above Caddyfile in production; I know it’s messy haha. It is purely just for me to learn and test stuff out.

4. Error messages and/or full log output:


5. What I already tried:

  • For the line rewrite @work index.html, I’ve tried / and /index.html as well

  • Looking through documentation to see if I’m missing something.

  • tried seeing if changing the order of the file_server directive has any impact.

  • Try adding a second Javascript file that just writes to the console but that is getting interpreted as HTML for some reason even though the other Javascript file on the page was properly parsed as javascript

6. Links to relevant resources:


Any help is appreciated! If I’m completely going in the wrong direction, please don’t hesitate to point that out. Will keep trying in the meantime!

Just so we’re on the same page, could you give a list of pairs of URLs and which file you expect them to load? For example:

/ -> /index.html
/app -> /index.html
/work/app -> /index.html
/work/app.min.js -> /app.min.js

I’d like to better understand where /work comes into play here. What’s the purpose of that subpath?

I think you might be looking for try_files, which is typically what’s used for setting up fallbacks for paths that don’t map to a file on disk:

Something like this:

try_files {path} /index.html

A typical config for a simple SPA usually looks like this:

:80 {
	root * /srv
	encode gzip

	try_files {path} index.html


That might be all you need.

Exactly, that’s basically what I’m looking to make happen. Anything with a file extension would resolve to the file, everything else will load up index.html.

Really just for semantic purposes - no real meaning behind it other than to have it help make up a nicer path. Basically the site is set up with 2 “pages”, home page + some detail pages. a full url might look like /work/<project 1>, etc…

Appreciate the sample! I just tried it though and unfortunately, with this, the paths don’t appear to resolve correctly; for instance instead of /app.min.js it looks like the request ends up being /work/app.min.js

That didn’t answer my questions. Please make a list of the request paths and their expected result.

This is getting crazy complicated, crazy fast. I’d probably advise starting from scratch and taking a simple approach.

Maybe something like this:

:80 {
  root * /var/html/www
  encode gzip

  try_files {path} {file} /index.html

This should satisfy your “all paths should load / under the hood, keep URL intact, and static files should still work”. Basically the same as @francislavoie’s but with {file} there to catch static assets in web root.

Caddy should try the static file if it exists first. If it doesn’t exist, it’ll check for the file in the web root. If that doesn’t exist, it’ll give you the index.

1 Like

But… why?

Just update your index.html to load the JS/CSS assets from absolute paths.

Right now I’m just trying to make sure I understand how to set things up right now before moving to prod so I’m ok was a little chaos; that said, yours is way cleaner and works better haha.

I am however running into a similar issue as before for some reason though with a slight improvement in that I can confirm at least that JS is getting evaluated, however some files are getting evaluated incorrectly, for example the browser thinks files ending in .jpg are html

Since we are falling back from the path, to the static file in root, to a HTML document, this will happen whenever the client requests a file that does not exist on disk.

AH ok I’m a dummy, that makes sense for that.

Some of my JS is still not running though, in test.js I have console.log that outputs a message as expected, but the main app file doesn’t seem to run; which is weird cause it runs fine when you navigate to /

Any ideas by chance? It appears to come through appropriately as a JS file which makes things weirder ha.

EDIT : AH HA I’m a dumbass, I forgot the app is dependent on some thumbnails loading first before anything else will load. Will give a shot at fixing that first

Apologies, what you had was basically what I was looking to do so I did not see the need to be specific, but if I have to list routes out.

  • / should load /index.html
  • /work/project should load `/index.html’

Any path that ends in a file extension should load whatever that file is.

Apologies but could you clarify just a tiny bit? Right now my folder structure looks something like


To keep things simple lets just worry about the javascript which are in the html as script tags. I have tried

  • app.min.js
  • /app.min.js
  • ./app.min.js

Is there another way to write the filepath that I’m forgetting? I would rather not include the full url as part of the filepath.

Right now without re-writing, if I were to navigate to /work/project , the request for the JS shows up in the browser as /work/project/app.js

In your index.html, how do load your app.min.js? It should look like this:

<script src="/app.min.js"></script>

If you’re doing something like below, it’s wrong (you should have the / in front of the filename, i.e. an absolute path):

<script src="app.min.js"></script>

That way, no matter the URL, the browser will always request app.min.js from the root, and not relative to the current URL.

Ok so part of the problem was I was a dummy and forgot how my own code worked. There are some thumbnails that are preloaded before the page is allowed to load that I forgot about while trying to figure this out. I guess this problem frustrated me more than I thought.

Anyways after a little more reading I’ve figured how to do what I want to do utilizing what Whitestrake and francislavoie suggested

In case anyone ever has a similar issue, here is what works for me for a site that only has at most one extra core route ie

:80 {
    root * /var/html/www
    encode gzip

    route /work/* {
          uri strip_prefix /work
          try_files {path} {file} /index.html





with whatever the name of your route is in your JS.

I’m sure there’s a better more dynamic way to do this(I think /work/ could be replaced with regex?) but this works for me.

You can use handle_path for this:

    handle_path /work/* {
          try_files {path} {file} /index.html

You also probably don’t need {file} anymore I think.


Ah awesome, thanks for the tip!

This topic was automatically closed after 30 days. New replies are no longer allowed.