diff --git a/assets/js/app.js b/assets/js/app.js
new file mode 100644
index 0000000..1bbc1bd
--- /dev/null
+++ b/assets/js/app.js
@@ -0,0 +1 @@
+console.log('Hello, World!')
diff --git a/config/config.exs b/config/config.exs
index f63b335..81f00f4 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -6,9 +6,17 @@ config :tailwind,
   version: "3.4.1",
   default: [
     args: ~w(
-    --config=tailwind.config.js
-    --input=css/app.css
-    --output=../priv/output/assets/app.css
-  ),
+      --config=tailwind.config.js
+      --input=css/app.css
+      --output=../priv/output/assets/app.css
+    ),
     cd: Path.expand("../assets", __DIR__)
   ]
+
+config :esbuild,
+  version: "0.20.1",
+  default: [
+    args: ~w(js/app.js --bundle --target=es2016 --outdir=../priv/output/assets),
+    cd: Path.expand("../assets", __DIR__),
+    env: %{ "NODE_PATH" => Path.expand("../deps", __DIR__) }
+  ]
diff --git a/lib/sloane_sh/watch.ex b/lib/sloane_sh/watch.ex
index d96e37c..8313e29 100644
--- a/lib/sloane_sh/watch.ex
+++ b/lib/sloane_sh/watch.ex
@@ -35,6 +35,10 @@ defmodule SloaneSH.Watch do
       Tailwind.install_and_run(:default, ~w[--watch])
     end)
 
+    Task.start_link(fn ->
+      Esbuild.install_and_run(:default, ~w[--watch])
+    end)
+
     state = %__MODULE__{ctx: ctx, watcher_pid: watcher_pid}
 
     {:ok, state, {:continue, :build}}
diff --git a/mix.exs b/mix.exs
index 04ea82e..ed9cc1f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -29,7 +29,8 @@ defmodule SloaneSH.MixProject do
       {:plug, "~> 1.15"},
       {:bandit, "~> 1.2"},
       {:tailwind, "~> 0.2"},
-      {:toml, "~> 0.7"}
+      {:toml, "~> 0.7"},
+      {:esbuild, "~> 0.8"}
     ]
   end
 
diff --git a/mix.lock b/mix.lock
index 5ddf801..b785465 100644
--- a/mix.lock
+++ b/mix.lock
@@ -3,8 +3,10 @@
   "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
   "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
   "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+  "esbuild": {:hex, :esbuild, "0.8.1", "0cbf919f0eccb136d2eeef0df49c4acf55336de864e63594adcea3814f3edf41", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "25fc876a67c13cb0a776e7b5d7974851556baeda2085296c14ab48555ea7560f"},
   "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
   "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
+  "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
   "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
   "plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
   "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
diff --git a/priv/site/layouts/root.html.eex b/priv/site/layouts/root.html.eex
index 93b89c5..d6664bc 100644
--- a/priv/site/layouts/root.html.eex
+++ b/priv/site/layouts/root.html.eex
@@ -8,5 +8,6 @@
   </head>
   <body class="w-full max-w-3xl bg-white text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100">
     <%= inner_content %>
+    <script defer src="/assets/app.js"></script>
   </body>
 </html>