Migrating Next.js App to GO × Templ × HTMX
Intro
Recently, I just rewrite one of my application Stashbin from Next.js to GO. Though my main motivation of this migration was to learn GO and experimenting with HTMX. I also aiming to reduce the resource usage of my application and simplify the deployment process. Initially, Stashbin codebase are split into two seperate repository, one for the frontend that uses Next.js and another for the backend that already uses GO. The backend repository is just a REST API responsible for storing and retreiving data from the database.
With the migration, I want to combine both frontend and backend into a single repository and serve both the API and the web application from single application. This will simplify the deployment process and reduce the resource usage of the application.
I also mentioned on my previous post that I’m already so frustrated with React further more Next.js. I’m not saying that they were bad, I just don’t enjoy working with it anymore.
So, without further ado, let’s get started.
The Migration
Planning and Preparation
In this project, I’m planning to merge the backend and frontend into a single monolith application. Both of them will be served from a single web server in Go. I’m planning to use several libraries to get the project done:
- Echo for the web server.
- sqlx for the database driver.
- Templ for the templating engine. Although Go already have a decent templating engine, I’m planning to use Templ because it’s more powerful and flexible. I really like this library and I’m planning to use it in my future projects.
Migrating the Backend
Migrating the backend is quite simple as the previous backend is already written in Go. There will be only a few adjustments that I need to make since I’m planning to drop the ORM and use sqlx instead. All i need is to change the query done by the ORM to basic sql query.
Migrating the Frontend
This is the most challenging part of the migration. I need to rewrite the frontend from Next.js to Go (Templ syntax). I also use HTMX to replace React’s functionality, communicating with the backend, and updating the DOM.
Here’s some example of the Templ syntax for the main page:
package view
import "github.com/mrmissx/stashbin/view/layout"
import "github.com/mrmissx/stashbin/view/icons"
templ Home() {
@layout.BaseLayout() {
<form hx-post="/api/document" hx-trigger="keydown[keyCode === 83 && (ctrlKey || metaKey)]">
@layout.Header() {
@icons.Save()
}
<main>
<textarea
id="editor"
name="content"
placeholder="Write or paste, save and share the link..."
></textarea>
</main>
</form>
@layout.Footer()
<script>
document.addEventListener("keydown", (e) => {
if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) {
// prevent default Ctrl+S or Cmd+S
e.preventDefault();
return
}
}, false);
</script>
}
}
With Templ, I can just import a layout and component like what React does with the @
syntax. I also use the hx-*
attribute to communicate with the backend using HTMX API. In build time, Templ will generate a Go code that represents the HTML code above and compile it to the final binary file.
Deployment
With the new monolith application, I can simplify the docker container deployments. I just need to build the application and run the binary file. I don’t need to build the frontend and backend separately and deploy them to different containers.
FROM golang:1.21-alpine AS build
WORKDIR /app
RUN apk update && \
apk add --no-cache nodejs npm
COPY go.mod go.sum package.json package-lock.json ./
RUN go mod download && \
go mod verify && \
go install github.com/a-h/templ/cmd/templ@latest \
npm install
COPY . .
RUN templ generate && \ # generate the Go code from the Templ syntax
npm run build # run the tailwindcss build
RUN go build -o stashbin # build the final binary
FROM alpine
WORKDIR /app
COPY --from=build /app/stashbin .
COPY --from=build /app/public ./public
CMD ["./stashbin"]
This is the Dockerfile that I use to build the application. I use a multi-stage build to build the application and copy the final binary with all assets to the final image. We will get the most minimal image size as possible.
Conclusion
With this migration, I’m able to reduce the resource usage of my application, simplify the deployment process also improve the developer experience. Here’s some of the improvements that I can see:
Image Size
The most significant improvement can be seen immediately from the docker image size. With just single codebase in GO I can bring the image size down from >100MB
to just 16MB
. Both of them are already the best optimization using docker build step without shiping the build dependencies.
![Old image size](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0gm5vrtdim9l5r16ucn.png)
![New monolith image size](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvdmkf56r8gjbunt45dt.png)
Resource Usage
With the new monolith application, I can reduce the resource usage of Stashbin especially the memory usage. The old application uses around 150MB
of memory while the new GO application only uses 4MB
of memory.
There are many other minor improvements that I can’t mention here like build time, developer experience, deployment speed, etc. I’m happy with the result and I’m looking forward to experiment more with GO and HTMX. If you want to contribute or reach me out, you can check the repository here.
See you in the next post! Cheers!