My latest side-project requires a web front-end.

I’ve heard there are many frameworks in many languages for this. And to be honest, this is an understatement.

I first went for Svelte because a friend recommended this to me. I then had some issues with it an tried Solid which was also a no-go in the end. Both are Javascript framework and the node_module hell is actually a thing.

I may have mentioned it before on this site but: I enjoy Rust 🦀 quite a lot. So I searched for a solution on this side of the force and found out that Yew has matured quite a lot since I first saw it.

This is the technical solution I went with. And along the way, someone on Discord asked for a cheap way to host such an application. Here is my take at this question: can’t make cheaper than free thanks to Gitlab pages.

For the record, this very website is also hosted for free on Gitlab pages. It uses the Zola Static Site Generator which is written in Rust 🦀

The stack

Yew

What is Yew? (Baby don’t hurt me 🎶 No more 🎵)

Yew self describes itself as A framework for creating reliable and efficient web applications.. It’s written in Rust 🦀 and is heavily inspired by both React syntax-wise and Elm architecture-wise.

In practice, an HTML page will load a WASM file and bootstrap it using a very tiny piece of Javascript. Both the WASM and JS files are automatically generated so all that is left to us is the Rust 🦀 code.

Gitlab pages

Gitlab allows anyone with an account to host publicly available pages for free.

To take advantage of this, one has to write a CI/CD pipeline with a pages job that outputs a public directory as part of its artifacts.

So, all that is left is to compile a Yew application in a Gitlab pipeline. Well, almost.

The implementation

I’m not going to explain how to write a Yew application here. The Yew’s website has a both a tutorial and an extensive documentation. On top of it, the Yew’s repository contains a plethora of well written examples.

One thing to note though: while Yew supports Server Side Rendering, Gitlab pages only serve static content. This implies the application will only use Client Side Rendering.

Actually, there are two things to know before continuing. The Yew documentation makes use of the BrowserRouter. This is a valid choice for anyone who can set-up its own reverse proxy to serve the index.html page from every URI. But Gitlab pages does not allow such a fine-grained configuration. The HashRouter has to be used instead of the BrowserRouter for the application to be served the right way.

Pipeline

A first version of the pipeline could look like this :

1pages:
2 stage: deploy
3 image: rust
4 before_script:
5 - rustup target add wasm32-unknown-unknown
6 - cargo install --locked trunk
7 script:
8 - trunk build --release --dist public --public-url $CI_PROJECT_NAME
9 artifacts:
10 paths:
11 - public

Let’s break it into pieces.

  • The job’s name: is has to be pages for this to work.
  • The stage can be left out as it defaults to test but I use deploy as it’s what the job does after all.
  • The rust image is used for compilation of the Yew project.
  • Before actually compiling, some dependencies are needed: the wasm32 target to compile from Rust 🦀 to WASM and Trunk, the WASM bundler recommended by Yew.
  • The compilation is a simple process: tell Trunk to build the application in the public directory and use the $CI_PROJECT_NAME name as the URI the application will be served.
  • All is left is to tell Gitlab that there is a public repository to serve.

Done, it works!

But damn, it’s slow! Imagine waiting almost 15 minutes to deploy a simple webapp… Let’s speed things up!

Custom docker image

The previous pipeline spent around 10 minutes ensuring dependencies were available (the before_script part). And only a couple of minutes compiling the actual project (the script part).

What if the dependencies were already available?

1FROM rust:latest
2RUN rustup target add wasm32-unknown-unknown
3RUN cargo install --locked trunk
docker build -t registry.gitlab.com/$PROJECT .
docker push registry.gitlab.com/$PROJECT

Updated pipeline

It’s now possible to use the newly built and pushed Docker image. Thanks to it, both the wasm32 target and trunk are ready to use in a blink.

1pages:
2 stage: deploy
3 image: $CI_REGISTRY_IMAGE
4 script:
5 - trunk build --release --dist public --public-url $CI_PROJECT_NAME
6 artifacts:
7 paths:
8 - public

The job now takes less than three minutes to succeed.

The final improvement

1pages:
2 stage: deploy
3 image: $CI_REGISTRY_IMAGE
4 script:
5 - trunk build --release --dist public --public-url $CI_PROJECT_NAME
6 - cp public/index.html public/404.html
7 artifacts:
8 paths:
9 - public

The sixth line copies the index.html into a 404.html file. This is needed because Gitlab will serve the 404.html page if someone browse to any invalid page.

Usually, the 404 page is carefully design along with other pages. But here, the HTML code is generated by the webapp. So duplicating the code from the index.html ensure the user always land on a working app.

Now that I write those lines, I might be tempted to change the cp into an mv. After all, if there is no index, the 404 will do just fine in any circumstances.

Conclusion

Hosting a Yew application is cheap as free on Gitlab pages. All it takes is a working Rust 🦀 application and a small piece of CI/CD.

Make sure to use the HashRouter instead of the BrowserRouter and you’re done. I wrote a very simple application as a proof-of-concept here for anyone interested.