Skip to content

ArikRahman/Clojkstra

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.appmy-app.app
  • clojkstra/appmy_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

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors