This serves as a starting point for applications that wish to combine clients and their gateways to seamlessly share types and the necessary contexts, cookies, auth, roles etc.
After using Hono and Vite separately in docker compose with some hacky type sharing, the search began for ways to combine them into one client + backend service. A BFF-like pattern that Next.js' /api folder provides, but Vite on the front end.
Lots of credit is due to this really great post was created by A-yon on how to accomplish this. From there, basic SSR was implemented with Tanstack Router.
You might notice both package-lock.json and bun.lockb. This is intentional as we intend to keep this code compatible with both run times for as long as necessary. Dockerfile examples for both will be included soon.
Surface is deployed as a NodeJS (or Bun) app container with a Hono backend. Routes are first matched by Hono and the catch all is delegated to Tanstack Router and renders a client that builds and runs a dev server on Vite.
Routes are entirely flexible. You don't have to prefix a backend request with /api, unless of course you want to. If Hono matches a route it will handle it and the catch all is delegated to the client router -- Tanstack's relatively new but very full featured and production ready router. This template uses the familiar file system based routing pattern.
The loader paradigm follows the render as you fetch pattern. Newer frameworks like Remix, Astro, Modern and more have elected to pursue this way for good reason. Loaders in our case are provided by Tanstack Router and are run on the server during an initial request, and then on the client after subsequent navigation.
We considered injecting the backend service context into our router context and run loaders in a forked manner but ultimately a more simple approach was to treat loaders as single processes, which sometimes HTTP loop when being rendered server side.
Using the surface pattern, we can store auth in httpOnly cookies for quick session lookups and seamlessly pass state between the client and server with Tanstack Router to dehydrate and rehydrate state and injecting session data directly into the router.