A Clojure template for building static sites with shadow-cljs and UIx, featuring server-side rendering with client-side hydration.
- Server-Side Rendering (SSR): Pre-render pages for better SEO and faster initial loads
- Client-Side Hydration: Interactive components come alive after page load
- Code Splitting: Separate bundles for each page to minimize JavaScript payload
- Hot Reloading: Fast development with shadow-cljs
- Static Site Generation: Build fully static HTML files for deployment
- Modern Stack: Clojure, ClojureScript, UIx (React wrapper), shadow-cljs (via deps.edn)
- Nix package manager
- (Optional) direnv for automatic environment loading
- Java 11+
- Node.js 16+
- Clojure CLI tools
- Babashka (optional, for task automation)
-
Enter the development shell:
nix develop # or with direnv installed direnv allow
-
Run development commands:
# For development (in separate terminals): dev # Start shadow-cljs with hot reload server # Start Ring server for SSR # For production: build # Build complete static site serve # Serve the built site locally
-
Install npm dependencies (React only):
npm install # Installs React and React-DOM # or with Babashka bb install
Note: shadow-cljs is managed through deps.edn, not npm.
-
Development mode:
# Terminal 1: Start shadow-cljs clojure -M -m shadow.cljs.devtools.cli watch app # or bb dev # Terminal 2: Start Ring server (for SSR) clojure -M:server # or bb server
- Shadow-cljs UI: http://localhost:8080
- Development server: http://localhost:3000
-
Build for production:
bb build # or manually: clojure -M -m shadow.cljs.devtools.cli release app clojure -M:static
This generates static HTML files in
resources/public/
. -
Serve static site:
bb serve-static
Visit http://localhost:8000
├── src/
│ ├── main/ # Server-side Clojure code
│ │ ├── core.clj # HTTP server and routing
│ │ └── render.clj # SSR rendering logic
│ └── app/
│ ├── common/ # Shared CLJC code
│ │ └── components.cljc
│ ├── pages/ # Page components (CLJC)
│ │ ├── home.cljc
│ │ └── about.cljc
│ └── client/ # Client-side ClojureScript
│ ├── home.cljs # Home page hydration
│ └── about.cljs # About page hydration
├── resources/public/ # Static assets and generated HTML
├── dev/ # Development and build tools
│ ├── build.clj # Static site generator
│ └── user.clj # REPL utilities and helpers
├── deps.edn # Clojure dependencies
├── shadow-cljs.edn # Shadow-cljs configuration
├── package.json # NPM dependencies
├── bb.edn # Babashka tasks
├── flake.nix # Nix flake configuration
└── .envrc # direnv configuration
- Universal Components: Pages are written in
.cljc
files, allowing them to be rendered on both server and client - Server Rendering: The server uses
uix.dom.server/render-to-string
to generate HTML - Client Hydration: JavaScript bundles use
uix.dom/hydrate-root
to make the static HTML interactive - Code Splitting: Each page gets its own JS bundle (home.js, about.js) plus a shared bundle
- Dev Tooling: Build scripts and development utilities are kept in the
dev/
directory following Clojure conventions
- Create a new page component in
src/app/pages/mypage.cljc
- Create a hydration file in
src/app/client/mypage.cljs
- Add the module to
shadow-cljs.edn
::mypage {:init-fn app.client.mypage/init :depends-on #{:shared}}
- Add rendering function in
src/main/render.clj
- Add route in
src/main/core.clj
- Update
build/static.clj
to generate the static HTML
When using nix develop
:
dev
- Start shadow-cljs development server with hot reloadserver
- Start Ring server for SSR developmentbuild
- Build complete static site (JS + HTML)serve
- Serve the static site locally on port 8000clean
- Clean generated filesrepl
- Start Clojure REPL with dev toolsformat
- Format Clojure code with cljfmtlint
- Run clj-kondo static analysisupdate-deps
- Update deps-lock.json for Nix builds
If using Babashka directly:
bb clean
- Remove build artifactsbb install
- Install npm dependencies (React only)bb dev
- Start development serverbb server
- Start Ring serverbb build
- Build complete static site (JS + HTML)bb serve-static
- Serve static site locally
For reproducible builds using Nix (run outside nix develop
):
# Build the static site with Nix (reproducible)
nix build
# The output will be in ./result/
ls -la ./result/
# Or build and copy to a specific location
nix build && cp -r result/* /path/to/deployment/
Note: Use nix build
for CI/CD or when you need reproducible builds. Use build
inside nix develop
for faster local development.
The generated static files in resources/public/
can be deployed to any static hosting service:
- Netlify
- Vercel
- GitHub Pages
- AWS S3 + CloudFront
- Cloudflare Pages
Simply upload the contents of resources/public/
to your hosting provider.
The dev/
directory contains development and build utilities following Clojure conventions:
- dev/build.clj: Static site generator script
- dev/user.clj: REPL utilities and development helpers
- :dev alias: Includes dev tools on classpath for REPL development
- :static alias: Runs the static site generation
You can start a REPL with dev tools available using:
clojure -M:dev
Example REPL workflow:
;; Start development server
(start-server)
;; Generate static files
(generate-static)
;; Test individual page rendering
(render-home)
;; Clean generated files
(clean)
;; Stop server when done
(stop-server)
- Write components in
.cljc
files for universal rendering - Use
#?(:cljs ...)
reader conditionals for client-only code - Keep pages lightweight - heavy interactivity can be loaded after hydration
- Test both SSR and hydrated versions during development
- Build scripts and dev utilities belong in the
dev/
directory
This is free and unencumbered software released into the public domain.