Svelte 3 + slimPHP 4 : Guidance

Hello all, just wondering if anyone else here has done something similar?

Essentially I’ve written a backend API using slim 4, secured with Tuupola’s excellent JWT middleware.

The front end is written in Svelte and the index.html is served up by slim when you hit the / path. Note: I’m using svelte-routing for client side routing. eg:

    $app->get('/', function (Request $request, Response $response, $args) {
      $response->getBody()->write(file_get_contents('./index.html', true));
      return $response;

I’m hosting the app as a docker container using trafex/alpine-nginx-php7 image.

It all works well with the exception of one irksome problem: Once you’re on a client route other than “/”, hitting browser refresh returns a server side 404 or 401. Yes F5 hits the server and thats why.

Is there some other configuration I need to consider?

I know I can split front and backend into separate containers which would avoid this problem, however I am hoping to keep this in one repo and one build for simplicity’s sake (I’m writing it for a friend) if possible.

Calling out any other gotcha’s would also be welcome.

If I can solve this, my intention is to publish (yet another) slim4 starter repo.


Since routing is being done on client side, have you tried to wildcard any route on slim instead of just /?

I just tried it and in fact it works

$app->map(['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], '[/{params:.*}]', function (Request $request, Response $response) {
    $response->getBody()->write(file_get_contents(__DIR__ . '/index.html', true));
    return $response; 

Yes I do a have a catch all route on the server:

$app->any('/{any:.*}', function (Request $request, Response $response, $args) { ... }

I use svelte-routing as the client-side router.

Please note I have other server-side routes like /auth /clients etc on the same stack.

When you say you tried this and it works, can I assume you had other slimPHP routes (other than ‘/’) and that you were using a client side router and at the time when you hit F5 on the browser sitting on a non-root client-route the page just refreshes??? Have you got a Gist for this?

I dont have this problem with other deployments because the client is being served in a separate container using the sirv npm package an making api calls to an express GraphQL server in another container.

I would prefer not to separate the Svelte client into it’s own container. This issue is not a killer ATM as what I’m working on is a proof of concept but I can see it being one down the track.

I was shooting for a single container full stack vibe. FYI

I’ll be exploring caddy instead of nginx later or I might learn something in the mean time.

Thanks for your reply.

I tried with only one route before… But I’ve tried again with an “api”-like route serving some json and it seems to be working.

I’ve posted a little more than a Gist here:

Please, let me know if this is what somewhat you were trying to achieve.
Also, be aware I’m very new to svelte and node (in general), so I don’t know if I client routing is ok…

Why not serve using docker-compose? I also think caddy might be a good idea.

Thanks @raffster for your time on this. I cloned the repo and it works as you say.

What that then says to me is that my NGINX config is not quite right somewhere (no surprise there as I’m not an nginx guru).

None the less this is encouraging as I wont need to run separate containers, I just need to delve a little further. Although caddy is looking attractive for its built in support for letsencrypt.

I’ll pair down and generisize my current PoC and share the repor with you.


As it turns out it wasn’t my config of NGINX after all.

I implemented @raffster 's code but with my PHP/NGINX set up ( and surprise surprise it still worked.

Which then helped me realise it was 2 things:

  1. A facepalm moment when I noticed my catch all Slim4 route should have redirected to index.html instead of issuing a 404
 $response->getBody()->write(file_get_contents('./index.html', true));
 return $response;
 // $response->getBody()->write('404 not found');
 // return $response
 //         ->withStatus(404);

Which then let me to discover …

  1. I was using a reactive store incorrectly which meant that the isLoggedIn was falsy when it shouldn’t have been (ie was indeed loggedIn), so hitting refresh whist sitting /api-clients/view/1 it was falling through to the catch all of NotFoundA
		{#if !isLoggedIn}
			<Route exact path="/">
				<Login on:loggedIn={loggedIn}/>
			<Route path="*" component={NotFoundA}/>
			<NavBar on:logout={logout} />
			<Route path="/api-clients" component={ClientList} />
			<Route path="/api-clients/view/:id" let:params >
				<ClientView id="{}" />
			<Route path="/api-clients/edit/:id" let:params >
				<ClientEdit id="{}" />
			<Route path="*" component={NotFoundB}/>


Anyways, thanks @raffster for poking this bear and getting me over the hump!