8 ms·
Show HN: Use Go's HTML/template to write React-like code
Hi all,
I'm currently working on my hobby project, and one of the fun constraints I put in was to build using as less dependencies as possible.
I chose Go as it has a really good standard library, and all went well for building backend. But for frontend, I was wondering whether I should break the constraint and go for React. I tried options like Web Components, but I really didn't like the ergonomics and I didn't want to use jQuery either.
Out of curiosity, I was exploring Go's html/template package to see if I can write UIs in a React-like manner. I found most of the online docs using the "slots" like approach which I found unintuitive.
But after trial and error, I found an approach that's very close to React and without using any 3rd party packages like templ.
I'd like to share this with the community, not sure if it's a common approach - https://www.sheshbabu.com/posts/react-like-composition-using-go-html-template/ https://www.sheshbabu.com/posts/react-like-composition-using...
- rkwz 2y agoHere's the link: https://www.sheshbabu.com/posts/react-like-composition-using-go-html-template/ https://www.sheshbabu.com/posts/react-like-composition-using... Didn't realize HN doesn't convert link text to clickable links
- verdverm 2y agoGo's template system (both text/template and html/template) is pretty powerful. You can add custom functions and partial templates too. I use these concepts in https://docs.hofstadter.io https://docs.hofstadter.io to generate any code, not just frontend. Hugo uses the same system to do static site generation, which may be even closer to what you are showing. Helm templates is the epitome of the worst use (go/templating yaml files, I use CUE for this now). For frontend, as in your example, this has the older feel for UI, where each navigation requires a round trip to the server and a full-page response to the client. This is part of the reason people have moved to React and full-stack TS. I love Go, but I'm definitely a big fan of writing webapps in Next at this point. React Server Components are a really nice DX. I now generate much of the React / Next code at develop time, rather than runtime. Hof's original goal was to make adding a field to a type a one-line change in a source-of-truth file, and then generate all the changes through a full-stack app by running `hof gen`.
- rkwz 2y agoI love React too, but it pulls in a lot of dependencies and the dependencies need periodic maintenance. Sometimes you get the feeling that you're spending a significant chunk of your time not writing code but fighting with configs and dependency churn. Using Go's html/template definitely makes the UI feel dated because of re-rendering, but I rarely notice it in my side project. My next exploration is to use HTMX. I'm planning to gradually add dependencies like HTMX to see how far I can go to reach React's smoothness.
- verdverm 2y agoThere eventually has to be JavaScript somewhere, and once you have to use that for any dynamic frontend, might as well use a proper JS/TS framework. Not being a frontend person first, I have learned to choose whatever they are using these days. It will be hard to hire, onboard, and maintain anything that strays too far for the normal patterns they use. Next has skyrocketed in popularity for a reason. It makes the React DX much better, losing a lot of the config hell from other framework/library amalgamations
- rkwz 2y ago> There eventually has to be JavaScript somewhere, and once you have to use that for any dynamic frontend, might as well use a proper JS/TS framework. Yes, totally understand it's hard to escape JS in Web frontend. Even writing a simple dropdown requires a little bit of JS. > Not being a frontend person first, I have learned to choose whatever they are using these days. It will be hard to hire, onboard, and maintain anything that strays too far for the normal patterns they use. Since this is for my hobby project, it's more fun to be a irrational ;) I use standard React at work. > Next has skyrocketed in popularity for a reason. It makes the React DX much better, losing a lot of the config hell from other framework/library amalgamations Need to check Next again, I tried it many years back and it was good.
- 8organicbits 2y agoIf you really want, you can avoid JS for drop down menus too. I've seen the CSS hover [1] and the summary/details [2] approach before. My recent sites use Bulma for all the styling, so the only JavaScript I'm using is the toggle for the navbar burger icon on small screens [3]. [1] https://www.w3schools.com/Css/css_dropdowns.asp https://www.w3schools.com/Css/css_dropdowns.asp [2] https://codepen.io/Ajay-Anand/pen/OJoZjPd https://codepen.io/Ajay-Anand/pen/OJoZjPd [3] https://bulma.io/documentation/components/navbar/ https://bulma.io/documentation/components/navbar/
- lucideer 2y ago[off-topic comment but somewhat relevant for anyone choosing a lang/ecosystem] I noticed that the syntax highlighting for the Go html/template examples in the article is incomplete. We're using Go html/templates on a new project (within a team that does a lot of React frontend & some Python) so this kind of project seems like it would really suit, but a massive bugbear has been the very poor IDE-level syntax highlighting support for Go html/templates. Can anyone recommend a good syntax highlighter for Go html/template in VSCode for example? None of the main/official/popular addons seem to get this right. Seems a pretty unexpected oversight from a language from Google.
- rkwz 2y ago> I noticed that the syntax highlighting for the Go html/template examples in the article is incomplete. Sorry, it might be something wrong with my blog setup. I use hexo, might need to play around with the config. I use this VSCode extension for coding and it works fairly well: https://marketplace.visualstudio.com/items?itemName=jinliming2.vscode-go-template https://marketplace.visualstudio.com/items?itemName=jinlimin...
- catlifeonmars 2y agoThis is something that should be part of gopls IMO.
- dabber21 2y agothere are also projects that let you use HTML directly in Go like: https://www.gomponents.com/ https://www.gomponents.com/ (I'm currently using it in a side project and liking it so far, but I also just started)
- markusw 2y agoHey, I wrote that! :D I hope you find it useful.
- impune 2y agoIn Jet Brains family IDEs (not sure how they call the go one) you can use Inject language or reference option to get most of editor support (inspections,highlighting,intellisense etc.) for snippets of one language embedd in files of another language. I've been using it with JS scripts inside custom xml for a while and it works pretty well.
- senthil_rajasek 2y agoThis is nice. Thanks for sharing. When I heard "react" I immediately thought SPA and Event handling in the browser. This is more close to ASP .NET ( organizing views on the server side). This is very useful but this is not for handling events on the client. Thanks again for sharing.
- rkwz 2y agoYes, it's more about organizing your UI code/templates similar to a React codebase than running Go code in browser. I think apart from vDOM, a big contribution from React is how we organize code.
- fourside 2y agoYou are using “React-like” to mean something people don’t usually attribute to React, hence why there’s so much confusion in the comments. React did not invent component-driven code organization. I would say React is better known for reactive, declarative UI code (the reason for its name) and JSX.
- michaelcampbell 2y agoYeah this is more like go-template-jsx than "react"
- fourside 2y agoIt’s not even JSX-like templates in Go because one of the biggest advantages of JSX is that for any logic you just use JavaScript instead of having to learn a template language. It’s extending HTML in JS rather than extending HTML to allow for scripting. I agree with the parent comment that this is more like the Go version of ASP.NET templates.
- michaelnny 2y agoAlthough I don’t know Go programming language, I do appreciate people making stuff using as minimal dependencies as possible. I’m glad that I’m not the only one who has this habit.
- geek_at 2y agoit kinda looks like a small PHP boilerplate. Except with GO you still need some code to make templates render
- rkwz 2y agoThanks! Go makes it really easy to go minimal dependencies. I was able to build a website analytics project with just 2 dependencies (sqlite driver and bcrypt package) - https://github.com/sheshbabu/mouji?tab=readme-ov-file#philosophy https://github.com/sheshbabu/mouji?tab=readme-ov-file#philos...
- graemep 2y agoI am not a Go or frontend person, so I am a bit confused. I cannot figure out where any necessary JS is generated. The documentation for html/template only says it generated HTM, and a React alternative is presumably producing dynamic frontends (otherwise why would you be using react?). Am I missing something huge?
- rkwz 2y agoI apologize for the confusion. I meant React for component composition in codebase rather than for building SPAs. https://news.ycombinator.com/item?id=40491188 https://news.ycombinator.com/item?id=40491188
- esprehn 2y agoThis looks like pretty standard template based web apps like old school php with includes? Not sure I understand the React like approach?
- didip 2y agoMy style for building web app. I was never convinced that doing everything on JS layer is a good thing (regardless the backend language). But you need to show more interactivity to convince the JS people. I personally like this style and add HTMX for partial updates.
- szundi 2y agoDepends on what you want to do
- catlifeonmars 2y agoIt depends strongly on what you’re building. Interactivity is a property of the app and different types of apps have different interactivity requirements. For example a page that is more or less a UI proxy for CRUD operations typically does not require too much client side state. On the otherhand, a flow chart editor (any document editor really) needs to function largely without round trips to the server. If the web app is intended for mobile, you would expect to see many more interactions with limited bandwidth or no internet connectivity, so you would need to account for that. This is much more about application architecture than preference (although you can certainly choose to only work on applications that match your preferences).
- fallinditch 2y agoGood perspective, thanks, this is the kind of information that is often hard to find: deciding on a tech stack is nuanced, is more than preferences. I am looking for a good source of information about approaches to application architectures that is relevant to modern times.
- slmjkdbtl 2y agobut JS is the only answer to client side interactivity without hacks, there's nothing to be convinced
- qaq 2y agohttps://github.com/a-h/templ https://github.com/a-h/templ is a similar mature project that has JSX style templates but using Go instead of Javascript with good VS code support etc.
- chabad360 2y agoExcept that its syntax choices are a little odd, instead of making all code inside curly braces, there are some special keywords which only mean something if they are at the beginning of a paragraph (iirc).
- dlisboa 2y agoI'm not a full-time Gopher but I had a similar thought a few weeks ago, this idea of just having template partials for components and maybe a function constructor. Templ has a similar feeling but you can get most of what you want with pure Go. In Ruby-world there's the ViewComponent pattern/lib that Github came up with, a sort of HTML non-JS component, but I just can't really see why you need more than simple functions to do it. Haven't thought it all the way through, I expected the code to come out a little bit cleaner but Go being a bit more explicit is for the better, not much one can do here. But it's a valid idea: 80% of JS component libraries are just reimplementing "<button>" as "<Button>" with no added value atop the HTML.
- philo23 2y agoA bit off-topic, and maybe it was just an example made without too much thought put into it, but I've noticed there seems to be a lot of people now who write button/links like this: <button><a href="/blah.html">My Button</a></button> As far as I know that's technically invalid HTML. You can't have an <a> as a child of a <button> (or even the other way around.) I'm also ignoring the invalid type="submit" against the <a> tag in the example button component in the post. I think it stems from people who want their links to take on the default button styling, but I'm not 100% sure. Has anyone else noticed this trend at all?
- idontknowifican 2y agoyeah it is invalid html, and probably done for the ui only. now people are just copying it. would totally break if the button anchor was in a form
- szundi 2y agoMaybe this works on IE6 or something like that?
- rkwz 2y ago> As far as I know that's technically invalid HTML. You can't have an <a> as a child of a <button> (or even the other way around.) I'm also ignoring the invalid type="submit" against the <a> tag in the example button component in the post. It definitely feels weird to have anything other than text nodes inside <button>, didn't realize it's actually invalid HTML. I used this approach mainly to have the button component behave as a link button and a form submit button. Here's the actual code from my project - https://github.com/sheshbabu/mouji/blob/master/commons/components/button.html https://github.com/sheshbabu/mouji/blob/master/commons/compo... What are the drawbacks of this approach? I'm pretty sure I've used this before, can't recall any downsides.
- philo23 2y ago> I used this approach mainly to have the button component behave as a link button and a form submit button. Personally I think the "proper" way to do this would be use just <a> for links, and <button> for submitting forms, and then have the same CSS styles applied to both so that both <button>'s and <a>'s look the same visually if that's what you're going for. I'm not particularly familiar with Go, but I'd either make the wrapping tag name itself a template variable (maybe an enum between "Anchor" and "Button"? again not familiar with Go) or have two components one for forms and one for links. If I've understood the syntax, something like this? <{{.Tag}} class="button-container"></{{.Tag}}> But given both <button>'s and <a>'s have lots of different attributes to manage its behaviour and state (name/value/type/disabled vs href/rel/target etc) I'd personally split them into two separate components, even if it ends up doubling up some of the logic and styles. > What are the drawbacks of this approach? I'm pretty sure I've used this before, can't recall any downsides. As others have mentioned, while it's technically invalid browsers will still happily accept it so there's no real issue with it, personally I just find it a bit quirky to read. There's no harm in leaving it just as it is :)
- dncornholio 2y agoBuilding a new response for every action you make in the app not something I would compare React to. This is more akind to old school PHP.
- recursive 2y agoA growing faction of developers consider that to be a good thing.
- aw4y 2y agoamazing, great work. This would be very "reactive" adding HTMX.
- todotask 2y agoI remember the Astro web framework was written in Go before they moved to TypeScript. If there's a lot of JSX on the page and it needs to be escaped, what is the performance impact? Could you optimize the code to minimize slowdowns from templates? Hugo, Jet, and other template engines are certainly interesting, but I chose to write in the Go template, which I later migrated to Astro with TypeScript. This was due to the challenges of managing two codebases for a medium-sized site as a solo dev.
- jackothy 2y agoI've been doing something very similar in Rust recently using Askama or Maud for templates (components), optionally with Axum to include a server in the binary. The author mentions wanting to colocate templates with logic, Maud allows much tighter/automatic integration in this regard. This approach also synergizes almost perfectly with HTMX.
- rkwz 2y ago> Maud allows much tighter/automatic integration in this regard Interesting, can you share some examples?
- jackothy 2y agoIn the linked Go approach, and also with Askama, you have to define each template (component) twice. Once as a .html file, and then again as a struct inside a source file. With Maud, templates/components are only defined once, so you no longer have to worry about keeping the two in sync or colocating them. This is because Maud provides a macro for writing HTML with Rust-like syntax, so all your HTML (components) ends up inside regular Rust files, and you can refer to variables as usual. This really makes for almost seamless integration between Rust code and the HTML you serve.
- Klonoar 2y agoI do this but using hypertext, since the syntax is closer to JSX and less confusing for others to touch.
- jackothy 2y agoJust checked out hypertext! Have you noticed any other advantages over Maud, except for syntax?
- sgt 2y agoRecently rediscovered htmx and applied it to a Django project. It really does give that SPA feeling without much of a hassle, and it's also easier in a team setting where there's less stepping on other people's toes, which is often the case with a full blown Vue or React app.
- kissgyorgy 2y agoI like the experimentation, but it's still using templates. I was hoping something pure-Go code components without any template rendering at all. I was trying to do the same React-like Component framework, but for Python. No templates needed, you write 100% Python code. Here are some examples how it looks like for simple components: https://github.com/kissgyorgy/compone/tree/master/core/example_components https://github.com/kissgyorgy/compone/tree/master/core/examp... I use it in a Django project everywhere, no template rendering for custom views!
- pelme 2y agoWe have been doing something similar with https://htpy.dev https://htpy.dev the last ~6 months.
- codegeek 2y agoInteresting. I am actually doubling down on building web apps in Go using mostly standard library, powerful templates and sprinkle in with a library such as Alpine JS. Seeing some success so far and it makes me so much happier knowing that I don't have to deal with node_modules BS (well may be except tailwind for css). I actually want to test the limits of how far one can go without using a JS framework like React/Vue especially for smaller teams.
- flashgordon 2y agoThis was exactly my journey too. And I went from a server side templating to going deep into Nextjs/react and then back to htmx/go templates. Every time I think "oh this pattern would have been possible with react" I find a much simpler way. All that dependency bs and bloat was just not worth it.
- BaculumMeumEst 2y agoThis is the direction I've moved to as well. I've used ROR and Clojure w/ heavyweight frontends in the past, but I eventually came around to the idea that I value Go w/ stdlib to avoid dependencies/framework churn, have painless deployments, and get extremely solid performance w/ minimal memory footprint out of the box without doing anything special.
- distalx 2y agoI’m really interested in your approach to building web apps in Go. I used to build toy projects with Go using just the standard libraries. What are the current frameworks or tools that are popular or recommended for building web applications with Go these days?
- thdn 2y agoGo, Templ + Tailwindcss + AlpineJS
- Philip-J-Fry 2y agoThis is a nice approach I've followed myself before. It feels like common sense web development, just writing things in a way which allows them to be reused. A button will likely be the same on any page of the site, so make a button component. I don't think this is "React-like" though. Making components isn't specific to React. We've been doing that since ASP and probably earlier. Your views still need to know what components to render here. You can go further and just define a template function which takes a template name and a data object. Then render arbitrary children. That way your UI can be fully composed in code and the only thing your components need to know is how to render themselves. var templateFS = os.DirFS("./templates/components/") type Component interface { Type() string } type Button struct { Name string } func (b Button) Type() string { return "button.html" } type Div struct { Children []Component } func (d Div) Type() string { return "div.html" } type Page struct { Children []Component } func (p Page) Type() string { return "page.html" } func main() { component := Page{ Children: []Component{ Div{ Children: []Component{ Button{Name: "Test button"}, }, }, }, } templates := template.New("") funcs := template.FuncMap{ "exec": func(tpl string, data interface{}) template.HTML { var sb strings.Builder templates.ExecuteTemplate(&sb, tpl, data) return template.HTML(sb.String()) }, } templates = template.Must(templates.Funcs(funcs).ParseFS(templateFS, "*")) templates.ExecuteTemplate(os.Stdout, component.Type(), component) Your page.html file becomes <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{range .Children}} {{exec .Type .}} {{end}} </body> </html> For example
- Trufa 2y agoI 100% know it's a preference/style kind of thing, and I know the React/Node isn't perfect and comes with it's downfalls, but I whenever I have to code in anything other, I just miss the visual/mental model aspect of having your tsx code basically identical to your actual html. The mental load of going from this to func main() { component := Page{ Children: []Component{ Div{ Children: []Component{ Button{Name: "Test button"}, }, }, }, } to this <Page> <div> <Button> Test button</Button> </div> </Page>` or going from this: const main = () => ( <Page> <div> <Button> Test button</Button> </div> </Page> ) To, well... case in point, exactly the same thing is just so nice.
- pbhowmic 2y agoOMG! No! Having dealt with Go templates in the context of writing Helm charts, I would say this is not a a great idea. Please, invent a better templating language for Go, and then perhaps go down this path. That being said, I am not a great fan of templates in any language. Jinja2 might be a heck of a lot better than Go templates but it can still be hard to get right.
- Philip-J-Fry 2y agoWhat is so wrong with this approach that makes you exclaim "OMG! No!"? This is practically no different to any basic server side rendering framework in any other language. This is a very basic pattern used all the time.
- smarkov 2y agoI've been using Go's html/template for the last couple of months and all I can say is that it's still very much a toy. It's fine for basic loops and conditionals but anything beyond that is extremely limited and having proper reusable components is a constant fight against limitations, specifically because of the lack of being able to pass multiple arguments to a template. I wish the Go team had gone with something more akin to PugJS[1], which in contrast is very rich in features, flexible and dare I say fun to build components and pages with. [1] https://pugjs.org/api/getting-started.html https://pugjs.org/api/getting-started.html
- mbivert 2y ago> because of the lack of being able to pass multiple arguments to a template I'm not sure if this I understand the issue correctly, but one can pass multiple arguments, by wrapping them e.g. into a hash (https://go.dev/play/p/89gP42K8XRb https://go.dev/play/p/89gP42K8XRb): package main import ( "log" "os" "text/template" ) func main() { err := template.Must( template.New("").Parse("{{ .greeting }}, {{ .user.name }}!"), ).Execute(os.Stdout, map[string]any{ "user": map[string]any{ "name": "earthling", }, "greeting": "hello", }) if err != nil { log.Fatal(err) } } In my experience, the module is far from a toy, but it's not always obvious how to use it properly, at first.
- kunley 2y agoVery well written article, just the right amount of text, pictures and examples. Thank you!
- carom 2y agoThis is what I do but my components have three sections: script, styles, and the component. I include those in a page. The page is generally another component with head and body that fits into a base page template.
- reactordev 2y agoYou can use go’s builtin build features and embed the results into a http.FS. go generate npm build go build And declaring the resulting frontend build as an embed.FS using //go:embed dist/ var frontend *embed.FS And use that in a http.FS https://blog.logrocket.com/using-go-generate-reduce-boilerplate-code/ https://blog.logrocket.com/using-go-generate-reduce-boilerpl...
- peter_l_downs 2y agoYeah this is what I do to. The OP's approach is interesting but I think that using typescript/javascript/whatever to build your app, then "render" using a build step (like `vite build` or whatever) and embedding the resulting static bundles is the best of both worlds. That said, the JS ecosystem is so weird that I totally understand the urge to bail entirely and do things in Go. But JS/TS and all the related frameworks really are decent if you pick a reasonable subset of them.
- irq-1 2y agoYou can also use `go generate` to create all the .html routes/handlers at compile. Not that it would matter much in a small app.
- pard68 2y agoI have something kind of similar written in Common Lisp, however it uses stock HTML and then lisp. A template is a literal HTML template element, and it's referenced with custom elements. So `<my-el>` is in the file that will be compiled, say indet.html, and then there is a my-el.html file which is the reference that is inserted into the `<my-el>` as a child. This file is just stock HTML with a `<template shadowrootmode="open">` as the root element and then whatever in that. This is all rendered properly by the browser because it is just standard HTML. The slot element can be used to inject into these template els. All the lisp does really is recursively compile the HTML and then manage any logic writtel inside of `<script lang="cl">` tags It actually supports any language, long as you define the lang= name in a config file to tell the compiler how to process it. Ie for python `python="python -m '$'"` would then let the cl compiler know how to handle `<script lang="python">`.
- danskeren 2y agoI wanted to do something similar, but ultimately went with Hugo and Astro for my projects because I wanted to host it on Cloudflare Pages. Are there any similar hosting options available for this approach?
- lieblingautor 2y agoThis is very cool. I wonder if the query-param-based state management will become hairy to manage, but maybe it'll just inspire discipline in crafting the state props.
- panyanyany 2y agovery nice article! but it seems a little complicated to me.