Skip to content
Public template

About

Clojure engine and template for future works

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

⚡ Clojkstra

Repo

A ClojureScript + React (via Reagent + Re-frame) starter template designed for cloning. Build new SPAs by cloning this repo, renaming the namespace, and deleting the demo pages.


Table of Contents


Overview

Clojkstra is not a one-off app. It is a reusable starter template, engine, and vehicle for future projects. Every architectural decision is documented and every file is clearly labelled as either a framework file (keep it) or a demo file (safe to delete and replace).

The included SPA demonstrates:

  • Hash-based client-side routing (works on GitHub Pages with no server config)
  • re-frame event dispatch and subscriptions wired end-to-end
  • A reusable Reagent UI component library
  • Global notification toasts, loading overlays, and error banners driven by app-db
  • Feature flags, config tokens, and a runtime-updatable theme surface
  • A reg-event-fx effect handler library (navigate, set-title, localStorage, log, setTimeout)
  • Local ratom state patterns alongside app-db patterns

Tech Stack

PackageRoleVersion
ClojureScriptLanguage1.11
re-frameState management + event bus1.4
ReagentReact wrapper1.2
shadow-cljsBuild tool2.28
BunJS runtime + package manager1.1
Tailwind CSSUtility CSS (CDN in dev)3.x

All dependencies are FOSS-licensed. npm and node are never used — everything goes through bun / bunx.


Project Structure

Clojkstra/
├── src/
│   └── clojkstra/
│       └── app/
│           ├── core.cljs            # Entry point — init, hot-reload, mount
│           ├── db.cljs              # App-db schema and default state
│           ├── events.cljs          # All re-frame event handlers
│           ├── subs.cljs            # All re-frame subscriptions
│           ├── views.cljs           # App shell, layout, page dispatch
│           ├── routes.cljs          # hash router (no external deps)
│           ├── effects.cljs         # Custom re-frame effect handlers
│           ├── utils.cljs           # Pure utility functions
│           ├── components/
│           │   └── ui.cljs          # Reusable Reagent UI component library
│           └── pages/
│               ├── home.cljs        # Home page (demo)
│               ├── about.cljs       # About page (demo)
│               └── example.cljs     # Example page template (demo)
├── docs/
│   ├── index.html                   # GitHub Pages entry point
│   ├── 404.html                     # SPA redirect for deep links
│   └── cljs-out/                    # Compiled JS (git-ignored, generated)
├── shadow-cljs.edn                  # shadow-cljs build config
├── deps.edn                         # Clojure/ClojureScript dependency manifest
├── package.json                     # Bun scripts + JS devDependencies
├── flake.nix                        # Nix devShell
├── .envrc                           # direnv: use flake
├── .clj-kondo/config.edn            # Linter config
└── .cljfmt.edn                      # Formatter config

Getting Started

**Prerequisites**

  • Option A — Nix (recommended)
    nix develop     # or: direnv allow  (if you have direnv installed)
        

    This drops you into a shell with Java, Clojure CLI, Bun, clj-kondo, and cljfmt pre-installed. No global installs needed.

  • Option B — manual

    Install the following globally:

    Then install JS dependencies:

    bun install
        

**Dev Commands**

CommandWhat it does
bun run devStart shadow-cljs watch + dev HTTP server on http://localhost:8080
bun run releaseProduction build → docs/cljs-out/ (dead-code eliminated, minified)
bun run cleanRemove docs/cljs-out/ and .shadow-cljs/ cache
bun run reportGenerate a build size report → report.html
clj-kondo --lint src/Lint all ClojureScript sources
cljfmt check src/Check formatting without modifying files
cljfmt fix src/Auto-fix formatting in place
nix fmtFormat flake.nix with nixpkgs-fmt

The dev server serves from docs/ and hot-reloads on every save via shadow-cljs.


Architecture

**Data Flow**

User interaction
     │
     ▼
(rf/dispatch [::events/some-event payload])
     │
     ▼
reg-event-db / reg-event-fx   ← reads :db, returns new :db + optional :effects
     │
     ▼
app-db  (single immutable map — the only source of truth)
     │
     ▼
reg-sub  (layer 2: extract raw slice → layer 3: derive/transform)
     │
     ▼
@(rf/subscribe [::subs/some-value])
     │
     ▼
Reagent component re-renders  →  DOM update

Effects (HTTP, routing, localStorage, etc.) are handled by registered reg-fx handlers in effects.cljs — they are never called directly from view components.

**File Map**

FileKindPurpose
core.cljsframeworkEntry point — init, hot-reload, mount
db.cljsframeworkApp-db schema and default state
events.cljsframeworkAll re-frame event handlers
subs.cljsframeworkAll re-frame subscriptions
routes.cljsframeworkhash router (no external deps)
effects.cljsframeworkCustom re-frame effect handlers
utils.cljsframeworkPure utility functions
views.cljsframeworkApp shell, layout, and page dispatch
components/ui.cljsframeworkReusable Reagent UI component library
pages/home.cljsdemoHome page — counter + notification demo
pages/about.cljsdemoAbout page — stack info, file map, data flow
pages/example.cljsdemoExample page template for new features

**Framework vs. Demo Files**

  • Framework files form the reusable base architecture. They contain extension points and should be kept (and modified) when building a new app.
  • Demo files are example content that demonstrate how the framework is used. They are safe to delete entirely — removing them will not break the base architecture.

Routing

Clojkstra uses hash-based routing (/#/about, /#/example) so the app works on GitHub Pages without any server-side URL rewriting.

Routes are defined as a pure data structure in routes.cljs:

(def app-routes
  ["/" {""        :home
        "about"   :about
        "example" :example
        true      :not-found}])

To add a new route:

  1. Add an entry to app-routes in routes.cljs
  2. Create src/clojkstra/app/pages/my_page.cljs with a (defn page [] ...) component
  3. Require it in views.cljs and add a case branch in page-for-route
  4. Add a { :handler :my-page :label "My Page" } entry to nav-links in views.cljs

Navigate programmatically from anywhere:

(routes/navigate! :about)
;; or from an event handler via the :navigate effect:
{:navigate {:handler :about}}

Feature Flags

Feature flags live in db.cljs under :config :features:

:features
{:example-feature true
 :debug-panel     true}

Check a flag in a view via subscription:

@(rf/subscribe [::subs/feature-enabled? :my-flag])

Toggle a flag at runtime from an event:

(rf/dispatch [::events/toggle-feature :my-flag])

Set a flag programmatically:

(rf/dispatch [::events/set-config [:features :my-flag] false])

GitHub Pages Deployment

The app is built directly into docs/ so GitHub Pages can serve it from that directory.

**One-time setup:**

  1. The repo is already at https://github.com/ArikRahman/Clojkstra
  2. Go to Settings → Pages on that repo
  3. Set Source to Deploy from a branch, branch main, folder /docs
  4. Click Save

**Deploy a new release:**

bun run release
git add docs/
git commit -m "release: <version>"
git push

GitHub Pages will serve the updated build within a minute.

The docs/404.html handles the rare case where someone hits a non-root URL directly — it redirects them to index.html so the SPA router can take over.


How to Turn Clojkstra into a New App

This is the core workflow the template is designed for.

**Step 1 — Clone and rename**

git clone https://github.com/ArikRahman/Clojkstra my-new-app
cd my-new-app

**Step 2 — Replace the namespace root**

Rename src/clojkstra/ to src/my_app/ (underscores in paths, hyphens in ns names):

mv src/clojkstra src/my_app

Do a project-wide find-and-replace:

  • clojkstra.app → my-app.app
  • clojkstra/app → my_app/app

Most editors support this with a single multi-file search-and-replace.

**Step 3 — Update config**

In db.cljs, set your app’s name and version:

:config
{:app-name "My New App"
 :version  "0.1.0"
 ...}

In shadow-cljs.edn, update :init-fn:

:init-fn my-app.app.core/init
:after-load my-app.app.core/on-reload

In package.json, update the "name" field.

**Step 4 — Delete demo files**

Remove the three demo pages:

rm src/my_app/app/pages/home.cljs
rm src/my_app/app/pages/about.cljs
rm src/my_app/app/pages/example.cljs

In views.cljs, remove the requires for those pages and add your own. In events.cljs and subs.cljs, delete all sections marked [DEMO]. In db.cljs, remove the :counter and :notifications keys.

**Step 5 — Seed your domain state**

Add your app’s initial data structure to db.cljs:

(def default-db
  {:current-route {:handler :home :route-params {}}
   :loading?       false
   :error          nil
   :config         { ... }
   ;; Your domain state:
   :current-user   nil
   :my-feature     {:items [] :loading? false}})

**Step 6 — Add your first real page**

Copy pages/example.cljs as a starting scaffold:

cp src/my_app/app/pages/example.cljs src/my_app/app/pages/my_feature.cljs

Update the namespace, follow the checklist at the top of the file, and add the route.

**Step 7 — Wire API clients and auth (optional)**

Add custom effect handlers in effects.cljs for HTTP, WebSocket, or analytics. Add :auth and :api keys to db.cljs. Gate in-progress work behind feature flags.


Extension Points

Each file has a clearly marked Extension point comment block. Here is a quick reference:

WhereWhat to add
db.cljsNew top-level keys for domain state; feature flags; theme tokens
events.cljsDomain event handlers; reg-event-fx for side-effectful events
subs.cljsDomain subscriptions; derived/computed layer-3 subs
routes.cljsNew route entries in app-routes
effects.cljsHTTP, WebSocket, analytics, native API effect handlers
utils.cljsPure helper functions with no re-frame dependencies
components/ui.cljsNew reusable Reagent components; or split into sub-namespaces
views.cljsNew page requires + case in page-for-route; new nav links
pages/One file per top-level page/feature

Linting and Formatting

**Lint:**

clj-kondo --lint src/

Configuration is in .clj-kondo/config.edn. re-frame registration macros are taught to clj-kondo so it understands reg-event-db / reg-sub / reg-fx without false positives.

**Format (check only):**

cljfmt check src/

**Format (auto-fix):**

cljfmt fix src/

Configuration is in .cljfmt.edn. Namespace :require blocks are intentionally not auto-sorted — manual grouping with framework/local comments is preferred.


Nix Dev Shell

flake.nix provides a fully reproducible development environment:

nix develop

Or with direnv:

echo "use flake" > .envrc
direnv allow

The shell includes: jdk21, clojure, bun, git, curl, jq, clj-kondo, cljfmt.

On entry, bun install runs automatically if node_modules/ is missing.

Format flake.nix itself with:

nix fmt

License

MIT — do whatever you want with it. The whole point is that you clone it and make it yours.

About

Clojure engine and template for future works

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages