npm package: @krs2k/simpler
Description: File-based routing framework for Hono with HTMX support, layouts, middleware, and Zod form parsing
Latest version: 1.0.2

# Simpler

Lightweight SSR framework built on [Hono](https://hono.dev), JSX, and [HTMX](https://htmx.org).
File-based routing, nested layouts, middleware, and Zod form validation — zero client-side JS frameworks.

## Quick Start

```bash
mkdir my-app && cd my-app
npm init -y
npm install simpler hono zod
npm install -D vite
```

Create your first page:

```
my-app/
└── src/
    └── pages/
        └── index.tsx
```

```tsx
// src/pages/index.tsx
export default function Home() {
  return <h1>Hello from Simpler!</h1>;
}
```

Run the dev server:

```bash
npx simpler dev
```

## CLI

| Command          | Description                          |
| ---------------- | ------------------------------------ |
| `simpler dev`    | Start development server with HMR    |
| `simpler build`  | Build for production                 |
| `simpler run`    | Run the production server            |

## Project Structure

```
my-app/
├── simpler.config.ts          # optional config
├── vite.config.ts             # optional (extra Vite plugins like Tailwind)
└── src/
    ├── htmx.d.ts              # optional — HTMX type support (see below)
    └── pages/
        ├── _layout.tsx        # root layout
        ├── _404.tsx           # custom 404 page
        ├── _error.tsx         # custom error page
        ├── _middleware.ts     # root middleware
        ├── index.tsx          # → /
        ├── about.tsx          # → /about
        └── blog/
            ├── _layout.tsx    # nested layout for /blog/*
            ├── _middleware.ts # middleware for /blog/*
            ├── index.tsx      # → /blog
            └── [slug].tsx     # → /blog/:slug
```

## Pages

Every `.tsx` file in `src/pages/` becomes a route. Files starting with `_` are special (layouts, middleware, error pages).

```tsx
import type { PageProps } from "simpler";

export default function About({ c, params }: PageProps) {
  return <h1>About</h1>;
}
```

### Page Data

Export a `pageData` function to load data before rendering:

```tsx
import type { PageDataFn } from "simpler";

export const pageData: PageDataFn = async ({ params, c }) => ({
  meta: { title: "My Page" },
});

export default function Page() {
  return <h1>Page with data</h1>;
}
```

## Layouts

`_layout.tsx` files wrap all pages in their directory and subdirectories. They nest automatically.

```tsx
import type { LayoutProps } from "simpler";

export default function RootLayout({ children, pageData }: LayoutProps) {
  return (
    <html lang="en">
      <head>
        <title>{(pageData?.meta as any)?.title ?? "My App"}</title>
        <script src="https://unpkg.com/htmx.org@2.0.4"></script>
      </head>
      <body>{children}</body>
    </html>
  );
}
```

## Middleware

`_middleware.ts` files apply to all routes in their directory and below. Uses Hono middleware format.

```ts
import type { MiddlewareHandler } from "hono";

const middleware: MiddlewareHandler = async (c, next) => {
  console.log(`${c.req.method} ${c.req.url}`);
  await next();
};

export default middleware;
```

## Dynamic Routes

- `[slug].tsx` → `:slug` parameter
- `[...rest].tsx` → catch-all wildcard

Access params via `PageProps`:

```tsx
export default function Post({ params }: PageProps) {
  return <h1>Post: {params.slug}</h1>;
}
```

## Forms with Zod

Validate `FormData` using Zod schemas:

```tsx
import { z } from "zod";
import { parseForm } from "simpler";
import type { PageProps } from "simpler";

const Schema = z.object({
  email: z.string().email(),
  password: z.string().min(6),
});

export default async function Login({ c }: PageProps) {
  if (c.req.method === "POST") {
    const result = parseForm(await c.req.formData(), Schema);
    if (result.success) return c.redirect("/dashboard");
    // result.errors — field-level error messages
  }
  return <form method="post">...</form>;
}
```

## HTMX

Simpler auto-detects `HX-Request` headers and skips the root layout for HTMX partial responses.

For TypeScript support of `hx-*` attributes, copy the type declarations into your project:

```ts
// src/htmx.d.ts — grab from node_modules/simpler/lib/htmx.d.ts
// or import the type augmentation:
import "simpler/htmx";
```

## Async Context

Per-request context using `AsyncLocalStorage`:

```tsx
import { createAppContext } from "simpler";

const { useContext, setContext, runWithContext } = createAppContext({
  user: null as string | null,
});
```

## Configuration

Create an optional `simpler.config.ts` in the project root:

```ts
import { defineConfig } from "simpler";

export default defineConfig({
  port: 3000,
  basePath: "/",
  defaultMeta: {
    title: "My App",
    description: "Built with Simpler",
  },
});
```

## Plugins

Extend Simpler with plugins that add routes and middleware. Define them in `simpler.config.ts`:

```ts
import { defineConfig, definePlugin } from "simpler";

const healthCheck = definePlugin({
  name: "health-check",
  routes: [
    {
      method: "get",
      path: "/api/health",
      handler: (c) => c.json({ status: "ok" }),
    },
  ],
});

const timing = definePlugin({
  name: "request-timing",
  middleware: [
    {
      handler: async (c, next) => {
        const start = performance.now();
        await next();
        c.header("X-Response-Time", `${(performance.now() - start).toFixed(2)}ms`);
      },
    },
  ],
});

export default defineConfig({
  plugins: [timing, healthCheck],
});
```

### Plugin API

A plugin is an object with a `name` and optional `middleware` / `routes` arrays:

```ts
type SimplerPlugin = {
  name: string;
  middleware?: PluginMiddleware[];
  routes?: PluginRoute[];
};
```

**Middleware** — Hono middleware, optionally scoped to a path (`*` by default):

```ts
{ path?: string; handler: MiddlewareHandler }
```

**Routes** — Hono handlers with a path and optional HTTP method (`all` by default):

```ts
{ method?: "get" | "post" | "put" | "delete" | "patch" | "all"; path: string; handler: (c) => Response }
```

Plugins can also be factory functions for reusable, configurable extensions:

```ts
const auth = (opts: { secret: string }) =>
  definePlugin({
    name: "auth",
    middleware: [{ path: "/admin/*", handler: jwt({ secret: opts.secret }) }],
    routes: [{ method: "get", path: "/login", handler: loginPage }],
  });

export default defineConfig({
  plugins: [auth({ secret: "s3cret" })],
});
```

Plugin middleware and routes are registered **before** file-based routes, so they can intercept requests.

## Vite Plugins

If you need extra Vite plugins (e.g. Tailwind), create a `vite.config.ts` — it's automatically merged with Simpler's internal config:

```ts
import { defineConfig } from "vite";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [tailwindcss()],
});
```

## License

MIT