diff --git a/README.md b/README.md
index c089561..61ee391 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@
- Page-based routing to `.tsx` files that export a `JSX` function in `./src/pages`
- Transpile `.ts` files in `src/client/blah.ts` via `website.com/client/blah.ts`
- Default, simple HTML5 layout with working frontend transpilation/bundling, or supply your own.
+- Live reload in development mode — pages automatically refresh when the server restarts.
- Optional CSS reset.
- Optional pico.css.
@@ -203,6 +204,30 @@ Test with curl:
curl -N http://localhost:3000/api/time
```
+### Live Reload
+
+In development mode (`NODE_ENV !== 'production'`), `hype` automatically injects a live reload script into pages using the default layout. When the server restarts (e.g. via `bun --hot`), the browser will automatically reload the page.
+
+If you're using a custom layout, you can add live reload by importing and rendering the `ReloadScript` component:
+
+```tsx
+import { ReloadScript } from "hype"
+
+export default ({ children, title }: any) => (
+
+
+ {title ?? "hype"}
+
+
+ {children}
+
+
+
+)
+```
+
+`ReloadScript` only renders in development mode — it's a no-op in production.
+
### utils
`hype` includes helpful utils for your webapp:
diff --git a/examples/live-reload/package.json b/examples/live-reload/package.json
new file mode 100644
index 0000000..ebbb398
--- /dev/null
+++ b/examples/live-reload/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "hype-live-reload-example",
+ "module": "src/index.tsx",
+ "type": "module",
+ "devDependencies": {
+ "@types/bun": "latest"
+ },
+ "dependencies": {
+ "@because/hype": "*"
+ },
+ "scripts": {
+ "start": "bun run src/server/index.ts",
+ "dev": "bun run --hot src/server/index.ts"
+ }
+}
diff --git a/examples/live-reload/src/client/main.ts b/examples/live-reload/src/client/main.ts
new file mode 100644
index 0000000..bc485dc
--- /dev/null
+++ b/examples/live-reload/src/client/main.ts
@@ -0,0 +1 @@
+console.log('Live reload is active in dev mode — no setup required.')
diff --git a/examples/live-reload/src/css/main.css b/examples/live-reload/src/css/main.css
new file mode 100644
index 0000000..7ba0262
--- /dev/null
+++ b/examples/live-reload/src/css/main.css
@@ -0,0 +1,29 @@
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ display: flex;
+ justify-content: center;
+ padding-top: 4rem;
+}
+
+section {
+ max-width: 500px;
+ text-align: center;
+}
+
+code {
+ background: #f0f0f0;
+ padding: 0.2rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.9em;
+}
+
+@media (prefers-color-scheme: dark) {
+ code {
+ background: #333;
+ }
+}
+
+.hint {
+ color: #888;
+ font-style: italic;
+}
diff --git a/examples/live-reload/src/pages/index.tsx b/examples/live-reload/src/pages/index.tsx
new file mode 100644
index 0000000..ed57a79
--- /dev/null
+++ b/examples/live-reload/src/pages/index.tsx
@@ -0,0 +1,8 @@
+export default () => (
+
+ Live Reload Demo
+ Run this with bun run dev, then edit this file.
+ The browser will automatically refresh — no manual reload needed.
+ Try changing this text and saving!
+
+)
diff --git a/examples/live-reload/src/server/index.ts b/examples/live-reload/src/server/index.ts
new file mode 100644
index 0000000..273680a
--- /dev/null
+++ b/examples/live-reload/src/server/index.ts
@@ -0,0 +1,5 @@
+import { Hype } from '@because/hype'
+
+const app = new Hype({})
+
+export default app.defaults
diff --git a/src/index.tsx b/src/index.tsx
index 4cbe8a5..882a01c 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -13,6 +13,7 @@ const CSS_RESET = await Bun.file(join(import.meta.dir, '/css/reset.css')).text()
const PICO_CSS = await Bun.file(join(import.meta.dir, '/css/pico.css')).text()
export * from './utils'
+export { ReloadScript } from './layout'
export type { Context } from 'hono'
const pageCache = new Map()
@@ -142,6 +143,11 @@ export class Hype<
if (this.routesRegistered) return
this.routesRegistered = true
+ // live reload endpoint (dev mode only)
+ if (process.env.NODE_ENV !== 'production') {
+ this.sse('/__hype/reload', () => {})
+ }
+
// healthcheck
if (this.props.ok)
this.get('/ok', c => c.text('ok'))
@@ -246,3 +252,4 @@ function findAvailablePort(startPort: number, maxAttempts = 100): number {
function render404(c: Context) {
return c.text('File not found', 404)
}
+
diff --git a/src/layout.tsx b/src/layout.tsx
index 650eb92..263cb25 100644
--- a/src/layout.tsx
+++ b/src/layout.tsx
@@ -1,5 +1,7 @@
import { type FC } from 'hono/jsx'
+const isDev = process.env.NODE_ENV !== 'production'
+
const Layout: FC = ({ children, title, props }) =>
@@ -17,7 +19,13 @@ const Layout: FC = ({ children, title, props }) =>
{children}
+ {isDev && }
+const RELOAD_SCRIPT = '{let c=false;const e=new EventSource("/__hype/reload");e.onopen=()=>{if(c)location.reload();c=true};e.onerror=()=>{e.close();let d=50;setTimeout(function r(){fetch("/__hype/reload").then(()=>location.reload()).catch(()=>{d=Math.min(d*1.5,2000);setTimeout(r,d)})},d)}}'
+
+export const ReloadScript: FC = () =>
+ isDev ? : null
+
export default Layout