How to use re-frame with Biff

In Understanding htmx I discussed some of the trade-offs of building a primarily server-side-rendered app. If you'd like to use Biff but for whatever reason you've decided that you'd prefer to use ClojureScript and React instead of htmx, I've created an example repo that demonstrates how to integrate re-frame with Biff. The steps are split up into separate commits.

Read on for commentary. You'll want to create a new Biff project before you start.

1. Migrate the re-frame template app to Biff

Diff. I generated package.json and package-lock.json by running npm install react react-dom; npm install --save-dev shadow-cljs. Then I generated a separate re-frame project via lein new re-frame reframe-template and mostly just copied and pasted the contents of that into my Biff project.

At this point, you can run clj -M:dev dev to start the backend, and then in a separate terminal tab run npx shadow-cljs watch app to compile the CLJS code. Open localhost:8080, sign in, and then you should see "Hello from re-frame." (I've structured the app so we still use server-side rendering for the landing page and authentication flows, and then after you've signed in you're taken to the re-frame app.)

In a subsequent step we'll have clj -M:dev dev start Shadow CLJS for us.

2. Rewrite the Biff example app

Diff. Ta-da... the app is now fully functional, including the websocket-powered chat. I went through the contents of app.clj and re-implemented it in CLJS.

I'm no re-frame expert; I studied it a bit several years ago but have never built anything with it. So there may be better ways to do things, but this should at least get you started.

The websocket integration in particular is not very robust. If you're actually planning to build something with websockets, you'll at least want to add reconnect logic, and maybe use Sente.

Although Biff provides an /auth/signout endpoint by default, that one redirects to the landing page afterward, which causes the browser to do an additional, unnecessary GET / when you sign out. So I added a different signout endpoint that just returns an empty response.

3. Update tasks so they run Shadow CLJS

Diff. We'll need to modify the dev, deploy, soft-deploy, and uberjar tasks so they compile your CLJS code. We also need to make Dockerfile install npm since it's used for CLJS compilation.

The general approach is that for each task we add a custom function to dev/tasks.clj, have it run the Shadow CLJS command, and then have it delegate to the original task function for the remainder. If we had to make deeper changes, we could copy the task functions' implementations into dev/tasks.clj instead of delegating.

We also update the :biff.tasks/deploy-untracked-files config option so that the deploy and soft-deploy tasks will copy our compiled CLJS to the server.

Published by Jacob O'Bryant on 5 Dec 2023

Sign up for Biff: The Newsletter
Announcements, blog posts, et cetera et cetera.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.