From 2c4488846e888ae5e633e33abbfd6ac30e08cbc8 Mon Sep 17 00:00:00 2001 From: b0txec Date: Mon, 24 Aug 2026 10:32:38 +0300 Subject: [PATCH] Add a general SPA fallback instead of an explicit per-route list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct hits on client-side routes not in the hardcoded list (e.g. a browser refresh on /faktiska or /udens-temperatura) 404ed instead of loading the app — a known limitation that was actually hit in production. Real files now serve as-is; anything else falls back to index.html so the SolidJS router handles it, matching the existing "TODO rewrite in more generic way" comment. Future routes need no backend changes. --- src/main/scala/server/Server.scala | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/scala/server/Server.scala b/src/main/scala/server/Server.scala index 177e36d..fa985e7 100644 --- a/src/main/scala/server/Server.scala +++ b/src/main/scala/server/Server.scala @@ -2,6 +2,7 @@ package server import cats.effect._ import cats.implicits.toTraverseOps +import cats.syntax.semigroupk._ import com.comcast.ip4s.IpLiteralSyntax import data.DataService import db.PostgresService @@ -203,18 +204,20 @@ class Server(postgresService: PostgresService, dataService: DataService, fetch: private val apiRoutesCors = CORS(apiRoutes, corsConfig) - private val httpApp = Router( - "/" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/api" -> apiRoutesCors, + // Real files (JS/CSS/images/favicon) are served as-is; anything else falls + // back to index.html so the SolidJS router can handle it client-side. This + // replaces an explicit per-route list that silently 404ed on a direct hit + // (e.g. a browser refresh) for any client-side route not on the list — + // real, observed on /faktiska and /udens-temperatura in production. + private val assets = staticcontent.fileService[IO](FileService.Config("./web/dist")) + private val spaFallback = HttpRoutes.of[IO] { + case req @ GET -> _ => + StaticFile.fromPath(Path("./web/dist/index.html"), Some(req)).getOrElseF(NotFound()) + } - // TODO rewrite in more generic way - "/station" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/cities" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/latvia" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/database" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/harmonie" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/lvgmc-forecast" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), - "/bridinajumi" -> staticcontent.fileService[IO](FileService.Config("./web/dist")), + private val httpApp = Router( + "/api" -> apiRoutesCors, + "/" -> (assets <+> spaFallback), ).orNotFound def run: IO[ExitCode] =