Iβve spent the last month building the backend of my project (Transportation Management System+CRM) in Go, while changing my mind daily on what to use for a frontend. I advocate for anything that emphasizes simplicity, and I know that is why I should use HTMX, but it does come with design limitations down the road. My JavaScript/Typescript experience is minimal, I consider it a necessary evil. Just by comparing the syntax of React vs. Vue vs. Svelte, it seems like a simple solution that Svelte is my answer.
I know some of you will hate hearing this, but I do chat with LLMs to get ideas and to learn (always verify, hence this post). When I asked what the tech-stack & file structure would look like when using Svelte with Go, it recommended SvelteKit over Svelte, with the reasoning being Server-Side Rendering. Forgive my ignorance (I coded in VB 6.0 & C++ from 1995-2002, then picked up Apex & Python in 2023), but it is my understanding that the purpose of using Go on the backend is to keep JavaScript on the client; as far away from the server as possible where I believe it should have stayed (thanks Ryan Dahl).
Can anyone spare a few minutes educating me on this topic?
Hi Anthony,
Chatting with LLMs is fine. They give you good ideas. But they tend to be upper average.
(But not really experts)
First of all, frontends are difficult. Not really from a technical perspective, even though you CAN actually mess something up if you do not plan it properly and then you have a BIG technical debt.
The problem is the user and that you have to βexplainβ intuitively what your app does. Users are lazy, dumb, nice, evil, and super smart at the same time. Today you cannot expect them to read a manual.
What to choose depends really on what you need to build. Will this be public? I mean do you need SEO for dynamic content? If No, I highly recommend CSR. (Even if the LLM says SSR)
Personal opinion:
My stack is React Vite and I am really happy with this. (But no experience with Svelte)
I cannot recommend HTMX. I posted about this. I think this is just a hype.
SSR is often just still in the heads for legacy reasons. I see the frontend as an actual app as every browser has awesome features and every device is actually a supercomputer. The backend is an API.
In the backend, Go is perfect, I am so happy every time I am allowed to use it. You will be fine.
Everyone I ever talked to was happy once they seriously tried Go
Hope I could help.
Well, it depends entirely on how you use Go. You can generate 100% of your site server-side in Go (using templates, etc.) and never use a front-end framework or JavaScript at all if you want. You can also use a hybrid approach where you generate HTML using Go but then use JS/front-end frameworks to do differential loads. You can also use a traditional SPA framework like React where you ship JavaScript to the client which then manipulates the DOM.
I am not super familiar with Sveltekit and how they handle SSR. But - for a client library to handle SSR you have to have some sort of runtime on the server. It looks like they have the concept of βadaptersβ for specific targets:
It looks like most people combining svelte and go are using βstaticβ adapter and then just serving that content up with Go. I donβt know how that would be able to prerender something with dynamic data from a RESTful API (you would need a server-side runtime for that). But maybe give the static adapter a try and see if it works for you.
I love the concept of everything being SSR, but for this project (which is completely internal, there is no need for SEO), I believe Iβm adding a layer of complexity (by my standards/skill set) by asking Svelte to SSR, in addition to Go.
@Dean_Davidson β regarding your post, could I keep my Go + Supabase backend, and simply write the frontend in Svelte as CSR? Itβs my understanding that Svelte is a compiler, and will turn the code directly into JavaScript; circumventing the concept of a Virtual DOM.
Yes. Probably the easiest way you could do this is: build your app using Svelte (which will leave you with JS/HTML), then serve that static content from your Go executable. I do a similar approach all the time. When doing deploys, have your front-end framework compile to ./public or something similar, then serve that static content like this:
The only other βgotchaβ to CSR is: they can land on any page in your app and/or hit refresh button so you need to either have a fallback that serves index.html on βnot foundβ or keep track of routes and serve index when a user lands on a route like yourapp.com/users/1:
// For all SPA routes we will also serve up /index.
// Register any front-end routes here
spaRoutes := []string{
"/home",
"/faq",
"/login",
"/users/{id}"}
for _, route := range spaRoutes {
http.HandleFunc(fmt.Sprintf("GET %v", route), indexHandler)
}
Where indexHandler just serves up index:
func indexHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./public/index.html")
}
There are other ways of accomplishing this as well but this is probably the simplest.
In terms of your API and how it interacts with your web app: you would just create routes that return JSON and your Svelte app would update the UI based on your RESTful API. Again - Iβm not a Svelte dev but something like this:
// main.go where you are setting up routes
http.HandleFunc("POST /api/items", getItemsHandler)
//...
func getItemsHandler(w http.ResponseWriter, r *http.Request) {
// Fetch items from wherever
items := getItems()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items)
}
Create a load function in svelte (or something similar):
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<h1>Items</h1>
<ul>
{#each data.items as item}
<li>{item}</li>
{/each}
</ul>
I donβt know about the virtual DOM in the svelte world. Frameworks use virtual DOMs because DOM manipulation is expensive. By keeping track of changes you can only update the DOM when you need to. So, Iβm not 100% sure how svelte manages itsβ DOM manipulation but I wouldnβt worry about that too much.
There is no reason why your Svelte App needs to be SPA. I have been coding with Go Backend and Svelte Frontend for years - and I usually go with a multi-page approach. Just serve multiple HTML files if you need multiple pages, if you need some persistent state on the client it can easily be shared in session-storage between pages.
How are you returning that server-side rendered as HTML with the title and content filled in? Or are you using what I would call a hybrid approach where you are just returning a blank title/content and the client uses the fetch API and sets title/content? This is the only part that confuses me about Svelte because in order to truly render things server-side you need some kind of runtime on the server.
But often the data will be reloaded on demand and dynamically with different queries (e.g. paging table data), so JavaScript does the loading and svelte dynamically updates frontend tables.
What I did occasionally to improve first page load performance for internal pages is to insert the data server side with go using the go template engine. But I insert the data as json directly into JavaScript and let svelte do the rendering into frontend components.
// go inserts data into this string literal:
const serverData=`{{data}}`
...
//Svelte passes the data down to components to safely render complex structured data
<Table data={data.rows}>
And for most public sites I use Atro as a build tool for svelte, which uses static rendering at build time and hydration, this gives perfect SEO performance without any server side requirements, since only user specific dynamic content is loaded via fetch API, giving a nice combination of fast caching for static data and fast rendering due to hydration.
Does this look accurate? Right now I have the home/main-dashboard written in Svelte, HTMX, and React. Trying to move forward with this project without breaking the βkeep things simple, stupidβ mantra.
There are several reasons why I prefer an approach with multiple pages.
Separation into smaller self contained modules: With an SPA you often have a lot of state in memory and many components become intertwined. In the end you have a big web of interconnected code and changing something at one end has the potential to break something somewhere else. With separate pages you have a clear boundary. So multiple developers can work in parallel at different pages and your tests donβt need to span page boundaries. Iβve seen SPAs where taking a certain path through the different pages lead to subtle bugs which were hard to find.
KISS - browsers are good with handing pages. You donβt need to simulate history, you have links/bookmarks which denote actual pages. You can jump into a page without needing to load/run any routing logic.
First load performance: Astro will statically build each individual page into pre-rendered HTML. So users jumping with a link to a certain page will get faster first load experience.
Fair. Thatβs mostly a design flaw because shared state is usually a bad idea unless itβs emitted over time as immutable values. The other bad thing that can happen with SPAs is memory leaks but most modern frameworks make that somewhat hard to do. But that is a totally valid criticism.
I think we could be building different types of apps which is why there are different tools for the job. Most of the time when I reach for a SPA itβs for heavily data-driven apps with very little public surface area (other than login/forgot password/etc). In some cases, SSR can help but the server has to know how to render more complex content from a database (like a report or something) and often I have large chunks of data so I need to virtualize the UI anyway.
Anyway, I havenβt used Astro but looking at it, it looks very cool.
Keep it simple and let it grow naturally is what I usually do! I think that jives with this comment from RSC:
For example, the vast majority of packages in the Go ecosystem do not put the importable packages in a pkg subdirectory. More generally what is described here is just very complex, and Go repos tend to be much simpler.
Thinking about your packages in terms of separation of duty is what usually leads to clean design. If I have multiple executables, I create a /cmd directory; if I donβt, I just put my single executable logic in a top-level main.go.
I honestly donβt worry too much about people importing my packages for private repos. A lot of what I do is only ever worked on by internal teams; so I usually only worry about /internal for open source projects and/or things where I expect other developers to consume my packages (like internal tooling used by other teams).
So with that in mind, hereβs a simplified version of a structure for a recent project I did. Itβs a very small/simple project so I have a simple layout:
# I like to have my data access layer mostly contained to
# a data folder but I usually like to split out models and
# queries. This makes it obvious when you import what you
# are using.
# Obvious this is a model of type User:
# loggedInUser := models.User{}
# Obvious that this is a query:
# resp := queries.GetReportData(myParams)
/data
/models
user.go
user_test.go
other.go #...
/queries
queries.go # general queries and package docs
reporting.go # break things up as needed
reporting_test.go
/mail # This app sends mail via SMTP
mail.go
mail_test.go
/github # This app has a github integration
github.go
github_test.go
/slack # This app also sends slack notifications
slack.go
slack_test.go
auth.go # Auth logic
config.go # App config
main.go # Our server
Anyway, thatβs it. This project started as a throwaway proof-of-concept for a new client (I literally started with a main.go as a solo dev and nothing else) and is now in production and being featured at tradeshows (it was at CES this year). Is this perfect? No way. But Iβll keep refactoring it as needed and breaking up packages if they become monoliths.
All of this, circling around, is to say: just start out simple and try to think about good package names with regard to how they will be used! mail.SendLoginNotication is easy to understand. As is github.CreateIssue, slack.NotifyBugReport, etc. If you havenβt read it, effective Goβs βpackage namesβ section is great:
Also take a look at this project that is very popular in the go ecosystem:
It doesnβt get any simpler than that. Keep it simple until you canβt! But thatβs just my $.02.
Yes, this has been a concern of mine with the React-like languages. Since returning to programming, my only experience has been creating our company website with NextJS, and the experience was like pulling teeth for me. Make a small change and the entire site goes down, but I guess that is the intrigue of βtype safeβ languages? IDK, this is a term I learned just last year.
This is exactly what a TMS is like, most of it is comprised of shipment data that is changing in real-time and needs to update in real-time without page refresh (Iβm assuming Websockets solves that problem?). The other half is API integrations with other third-party freight software (truck telematics, receiving Load tenders from shipper ERPs, integrations with auction boards). I love Astro, Iβve been creating small business websites with it for different members of my family, but I havenβt seen an instance of it being able to scale for what our needs are. Ultimately, I will open-source this platform and my plan is to monetize it via hosting on the server infrastructure Iβve built. Could have over a thousand users by the end of the year.
Awesome! Thanks for sharing, I will be checking this out.
httprouter is the default router for Go, is that accurate? Maybe Iβm confusing that. After a ton of research and not knowing what Iβm doing, I narrowed it down to httprouter, Gin, or Echo; but decided to roll with Echo because I have been led to believe it is simple and compatible (compared to Fiber which I hear people love, but it is built on fasthttp instead of net/http).
IDK, I am parroting a lot of what Iβve read, and the more I write code for myself, the more I find out what I enjoy; but the one truth that will always remain the same is that I prefer it to be as simple as possible without sacrificing user experience & productivity.
One more question β being from the 90s, I love a good desktop program (I refuse to call them applications, the Matrix didnβt and neither should you! ). This isnβt priority, but ultimately, I would like to make a desktop version of this (not Electron). Canβt this be done with something called Wails? If so, Iβm wondering if anyone here has any experience with this package.
Even with very dynamic data-driven apps I use Static Rendering to minimize page flickering or layout shifts while loading. With a static rendering approach a software like Astro pre-renders the view for loading data. I usually go with simple skeleton placeholders, so the shift in the page is minimal when the data actually arrives.
If it works perfectly the user sees a very small blocking time for loading the primary html and CSS and directly render a static version of the UI. Then hydration will spin up JavaScript and start loading the data and when the data arrives only the parts of the UI showing the data is updated (content of cells in a paginated table). With good caching display of the initial loading-state can be almost instant.
Without any SSR the page will have to load and parse JavaScript, create a virtual DOM with components and render them to HTML for the browser to display. This can lead to flickering/layout-shift while the user is waiting, while the browser has to update the render-tree multiple times while executing a ton of javascript code.