How to Serve A SolidJS App With A Go Backend

Tristan Isham 4 Feb 2025, 10:31 pm · edited

Single page applications (SPAs) are generally not the first tool I reach for when building a new website. Unless I’m starved for reactivity, I find managing user credentials, securing my API, and shipping a large bundle to the client to process frankly over-stressful and unnecessary. I’d much rather use a server-side multi-page app (MPA) with HTMX or Alpine.JS to achieve dynamic hydration where it counts without having to worry about exposing logic or secrets to the client.

However, recently I started building a realtime multiplayer board game using websockets. I needed a tech stack that could handle regular state changes from the server while updating a dynamic game board. Out of everything I found, Solid JS seemed to be the simplest, most performant framework I could integrate with Go.

Setting up Vite

You can set up a new Solid project easily by running

bunx degit solidjs/templates/ts my-app

Then move 'package.json', 'tsconfig.json', 'vite.config.ts', 'index.html' and 'postcss.config.js' out of your 'my-app' directory into the root of your Go project. This centers your root as the heart of your project and follows Go best practice. Next, modify the index html so the 'script' tag source is accurate to your 'index.tsx'.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <link
      rel="shortcut icon"
      type="image/ico"
      href="/client/assets/favicon.ico"
    />
    <title>My App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

    <script src="/my-app/index.tsx" type="module"></script>
  </body>
</html>

Finally, update your 'vite.config.ts'’s server.proxy object to support your application’s API route:

proxy: {  
     "/api": {  
       target: "http://localhost:3000",  
       changeOrigin: true,  
     },  
     "/ws": {  
       target: "http://localhost:3000",  
       changeOrigin: true,  
     },  
   },

Now, you can configure your backend to render your app’s development server and static assets depending on your development or production environment.

Using Go Fiber

DEBUG in this example represents any methodology you might use to determine if you're running your app in production.

	if DEBUG {  
		  
		app.Use(func(c *fiber.Ctx) error {  
			// Proxy requests to Vite dev server  
			if c.Path() == "/" || strings.HasPrefix(c.Path(), "/assets/") {  
				return proxy.Do(c, "http://localhost:5173")  
			}  
			return c.Next()  
		})

		app.Get("/*", func(c *fiber.Ctx) error {  
			// Redirect to Vite dev server  
			return c.Redirect("http://localhost:5173")  
		})  
	} else {  
		// Serve built assets in production  
		app.Use("/assets", filesystem.New(filesystem.Config{  
			Root: http.Dir("./dist/assets"),  
		}))  
		app.Get("/*", func(c *fiber.Ctx) error {  
			return c.SendFile("./dist/index.html")  
		})  
	}

Using Gin

if gin.Mode() == gin.DebugMode {  
        // Serve Vite in development  
        router.Use(func(c *gin.Context) {  
            if c.Request.URL.Path == "/" || strings.HasPrefix(c.Request.URL.Path, "/assets/") {  
                proxy := httputil.NewSingleHostReverseProxy(&url.URL{  
                    Scheme: "http",  
                    Host:   "localhost:5173",  
                })  
                proxy.ServeHTTP(c.Writer, c.Request)  
                return  
            }  
            c.Next()  
        })

        // Fallback route should be last  
        router.NoRoute(func(c *gin.Context) {  
            c.Redirect(http.StatusFound, "http://localhost:5173")  
        })  
    } else {  
        router.Use(static.Serve("/assets", static.LocalFile("./dist/assets", false)))  
        router.NoRoute(func(c *gin.Context) {  
            c.File("./dist/index.html")  
        })  
    }
Please create an account or sign in to leave a comment.
© 2025 Tristan Isham