Serving Astro with Rust(yieldcode.blog)
yieldcode.blog
Serving Astro with Rust
https://www.yieldcode.blog/post/serving-astro-with-rust/
21 comments
It took me a while to understand how little is ultimately going on here. If you're using Astro to generate a static site, then the question of how to serve it has nothing to do with Rust — it's a question you can ask (and which should be relatively easy to answer) for any web server.
Reading this, I can't help but feel like the "reject modernity, embrace tradition" UI development trend of returning to untyped templating languages is throwing the baby out with the bathwater. Using jinja/htmx/go-templates helps you avoid a big Javascript bundle, but it lands you right back in this 90s/00s toil of screwing around binding your "real" programming language to some half-baked untyped meta-language that spits out something XML-like. If you're picking Rust or any other language where you like the typechecker, why throw that all away?
Defining your UI as more-or-less normal functions in your main programming language that take normal arguments and return return composable UI-typed values is a massive, massive productivity win. Lots of teams pick Typescript/React over other languages/frameworks they prefer or that have better performance for only that reason!
I'm very happy that many production UI systems are moving towards a React/Elm-inspired API. On Android, Jetpack Compose means I don't need to screw around copy pasting method names between XML files and Java classes. On iOS, SwiftUI means I never need to plumb delegates around Interface Builder again. In Typescript land, there's a zillion React-inspired frameworks, like Astro, that carry this learning forward.
If I was in OP's position, I'd be looking for some macros / sugar (https://docs.rs/typed-html/latest/typed_html/ came up in a quick google search) that make it really easy to write composable UI snippets in my regular Rust files. Why introduce a new language, a new typechecker, and muck around iteroperating between Rust and Typescript, if you can do it all in Rust? You can even add a little bit of glue to make HTMX directives that eg do form things type-check against the form action they'll POST to. You should be able to rely on the Rust compiler to make function calls that return strings not error at runtime.
Defining your UI as more-or-less normal functions in your main programming language that take normal arguments and return return composable UI-typed values is a massive, massive productivity win. Lots of teams pick Typescript/React over other languages/frameworks they prefer or that have better performance for only that reason!
I'm very happy that many production UI systems are moving towards a React/Elm-inspired API. On Android, Jetpack Compose means I don't need to screw around copy pasting method names between XML files and Java classes. On iOS, SwiftUI means I never need to plumb delegates around Interface Builder again. In Typescript land, there's a zillion React-inspired frameworks, like Astro, that carry this learning forward.
If I was in OP's position, I'd be looking for some macros / sugar (https://docs.rs/typed-html/latest/typed_html/ came up in a quick google search) that make it really easy to write composable UI snippets in my regular Rust files. Why introduce a new language, a new typechecker, and muck around iteroperating between Rust and Typescript, if you can do it all in Rust? You can even add a little bit of glue to make HTMX directives that eg do form things type-check against the form action they'll POST to. You should be able to rely on the Rust compiler to make function calls that return strings not error at runtime.
This is the right answer.
How can you go back to a non-typed world after Rust I don't know.
I use html-node which has a typed submodule for even greater strictness. I just build some valid html, make a string and write that to the output directory and get on with my life. Eg: https://github.com/0atman/noboilerplate/
Templates... Rust functions
Flow control... Rust match expression
Abstraction... Rust modules
Html validation... Rust compiler
I like to think that in writing the html-node crate, Vidhan taught the compiler to speak html! https://lib.rs/crates/html-node
I use html-node which has a typed submodule for even greater strictness. I just build some valid html, make a string and write that to the output directory and get on with my life. Eg: https://github.com/0atman/noboilerplate/
Templates... Rust functions
Flow control... Rust match expression
Abstraction... Rust modules
Html validation... Rust compiler
I like to think that in writing the html-node crate, Vidhan taught the compiler to speak html! https://lib.rs/crates/html-node
Whenever I encounter a template language with expressions reaching into some view model or worse, I come back to an old idea of mine: what if you strictly wrote template first? The templates come up with names, types for the data identified by the names and nesting/optionally/repetition relationships, and the build process generates an interface for the programming language side to implement. Interface as in java interface, and curiously also as in typescript interface, despite the huge difference. A template-first viewmodel, including composability where a type is a reference to some other template.
Does this exist somewhere? My impression is that the tedious use of half-baked expression language to pull content from the (typed or not) programming language world into the template has always remained not quite enough of a pain point to drive this reversal and the rise of approaches that don't use separate templates at all (react et al) has made interface-from-template even less likely to happen.
Does this exist somewhere? My impression is that the tedious use of half-baked expression language to pull content from the (typed or not) programming language world into the template has always remained not quite enough of a pain point to drive this reversal and the rise of approaches that don't use separate templates at all (react et al) has made interface-from-template even less likely to happen.
Interesting idea!
As an example, if the template was "<span>{name}</span>" then if I follow you correctly, it would generate some `Model` struct/interface/whatever that would have a `name: String` field.
I think a problem comes about when it's instead:
"<span>{itemCount}</span>"
Obviously it can't know that this intends to be an integer. Would it generate an `itemCount` field that is something generic like "impl Display"? Or still String? Either way, you may have other logic that needs this `itemCount` as an integer, for arithmetic or something. Perhaps two models, both auto-generated for all String fields but one you don't touch and the other you can change fields as long as they're convertable to String.
I think the main problem is going from a more structured to less structured (model to template) is possible but going the reverse way (template to model) can't always infer the structure.
As an example, if the template was "<span>{name}</span>" then if I follow you correctly, it would generate some `Model` struct/interface/whatever that would have a `name: String` field.
I think a problem comes about when it's instead:
"<span>{itemCount}</span>"
Obviously it can't know that this intends to be an integer. Would it generate an `itemCount` field that is something generic like "impl Display"? Or still String? Either way, you may have other logic that needs this `itemCount` as an integer, for arithmetic or something. Perhaps two models, both auto-generated for all String fields but one you don't touch and the other you can change fields as long as they're convertable to String.
I think the main problem is going from a more structured to less structured (model to template) is possible but going the reverse way (template to model) can't always infer the structure.
Templates would of course have to define types, at least where string wouldn't be considered good enough. Also names for repetitions and optional blocks.
It would not solve any big problems when you start with a detailed plan and everything is going according to it. Perhaps that's the reason why fragile expression languages are so often considered good enough: it cements the importance of the person writing the spec (sorry for the sarcasm)
It would not solve any big problems when you start with a detailed plan and everything is going according to it. Perhaps that's the reason why fragile expression languages are so often considered good enough: it cements the importance of the person writing the spec (sorry for the sarcasm)
Well, you’d need to express the types of the arguments your template expects, and then your template type system and your host language type system need to agree on those types, and now you need to code-gen the host language struct type from the template args type - but what’s the point? It’s still needless toil in my view.
Just write a function in the host language, it already has a great way of expressing the types of arguments passed to a function (hopefully)
Just write a function in the host language, it already has a great way of expressing the types of arguments passed to a function (hopefully)
[deleted]
All of this can be achieved using nginx or apache.
Crazy that rust stuff just gets upvoted.
Also “server side rendering” as a term just needs to go away. Serving HTML is the way it all started.
Crazy that rust stuff just gets upvoted.
Also “server side rendering” as a term just needs to go away. Serving HTML is the way it all started.
I had to re-read OP's post multiple times just to understand that he was trying to serve static HTML pages alongside a Rust application at /api. It's not very clear, he only mentions the Rust server for the first time at the last section of the article. And yeah, he could have still used nginx.
Server side rendering is different from serving static HTML or template generated HTML.
What's the difference between "Server side rendering" or "template generated HTML"? Stuff like React is just fancy HTML templates.
They are the same thing. Template-generated HTML has been called server-side rendering for decades.
People immersed in development using front-end JavaScript frameworks have been sufficiently isolated from server-side rendering for long enough that it’s a foreign concept.
A recent optimisation in front-end frameworks is to generate the initial HTML on the server instead of sending an empty shell that does nothing but load the JavaScript application.
As you point out, this is just server-side rendering, which is a very old concept. However, if you have spent the majority of your time with front-end frameworks, “SSR” is a new concept that has recently been invented, not that old thing that people used to do before front-end JavaScript frameworks took over the world.
People immersed in development using front-end JavaScript frameworks have been sufficiently isolated from server-side rendering for long enough that it’s a foreign concept.
A recent optimisation in front-end frameworks is to generate the initial HTML on the server instead of sending an empty shell that does nothing but load the JavaScript application.
As you point out, this is just server-side rendering, which is a very old concept. However, if you have spent the majority of your time with front-end frameworks, “SSR” is a new concept that has recently been invented, not that old thing that people used to do before front-end JavaScript frameworks took over the world.
I think you're missing the point that people don't necessarily want to deal with either nginx or apache, and might want to only have whatever application they're working on present an http(s) interface/api directly from itself.
I've done that sort of thing explicitly in the past, with great results, which is why I think you overlooked such.
I also think you're missing the idea that server side rendering implies a class of network traffic reductions, with resulting UX improvements.
An example might would be that a subset of possible javascript, which might have been sent to a browser which was "Serving HTML", might be responsible for resulting network requests right back to the server serving the html.
And this pointless network traffic which uses nothing on the browser except a javascript engine might be identified and run with no network overhead on the server, which is serving whatever in the first place. i.e., it's a network traffic 'optimization' since latency is a big deal in UX.
You could read up on server side rendering ideas at https://bun.sh or https://deno.com for just two examples.
I've done that sort of thing explicitly in the past, with great results, which is why I think you overlooked such.
I also think you're missing the idea that server side rendering implies a class of network traffic reductions, with resulting UX improvements.
An example might would be that a subset of possible javascript, which might have been sent to a browser which was "Serving HTML", might be responsible for resulting network requests right back to the server serving the html.
And this pointless network traffic which uses nothing on the browser except a javascript engine might be identified and run with no network overhead on the server, which is serving whatever in the first place. i.e., it's a network traffic 'optimization' since latency is a big deal in UX.
You could read up on server side rendering ideas at https://bun.sh or https://deno.com for just two examples.
Can you embed the html files into the binary using Rust like you can with Go?
One of the coolest features of Go is the embed package. You can build, and bundle a React, Vue, SvelteKit, Astro or w.e framework directly into your binary and have it serve with your API. Makes deploying very easy, just ship the single file binary.
One of the coolest features of Go is the embed package. You can build, and bundle a React, Vue, SvelteKit, Astro or w.e framework directly into your binary and have it serve with your API. Makes deploying very easy, just ship the single file binary.
It's a utf8 string though, Go gives you a reader which is any type of data.
Why not just use Zola at this point?! You don’t need js either.
Did OP read Caddy doc before throwing Rust into the mix? I'm pretty sure his use case is doable with simple Caddy config.
[deleted]