@mauroandre/velojs 0.0.37 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: velojs-getting-started
3
+ description: "Scaffold a new VeloJS project and its file layout: `velojs init`, `vite.config.ts`, `app/routes.tsx`, the root HTML shell, dev server. Use when starting a project from scratch or wiring up the toolchain."
4
+ ---
5
+
6
+ # Getting Started
7
+
8
+ VeloJS is a fullstack web framework that combines Hono (server), Preact (UI), and Vite (build) into a single, cohesive experience. You write your pages, loaders, and server actions in one place — VeloJS handles the rest.
9
+
10
+ ## Create a new project
11
+
12
+ The fastest way to start is with the CLI:
13
+
14
+ ```bash
15
+ npx @mauroandre/velojs init my-app
16
+ cd my-app
17
+ npm install
18
+ npx velojs dev
19
+ ```
20
+
21
+ This creates a ready-to-run project. Open `http://localhost:3000` and you should see your app.
22
+
23
+ ## Project structure
24
+
25
+ Every VeloJS project follows this layout:
26
+
27
+ ```
28
+ my-app/
29
+ ├── app/
30
+ │ ├── routes.tsx # Where you define all your routes
31
+ │ ├── server.tsx # Server-only init (databases, APIs, etc)
32
+ │ ├── client.tsx # Client-only init (global CSS, etc)
33
+ │ ├── client-root.tsx # The HTML shell (<html>, <head>, <body>)
34
+ │ └── pages/ # Your pages, layouts, and modules
35
+ ├── vite.config.ts # Vite configuration (just one plugin)
36
+ ├── tsconfig.json
37
+ └── package.json
38
+ ```
39
+
40
+ Let's walk through each file.
41
+
42
+ ## vite.config.ts
43
+
44
+ VeloJS works through a single Vite plugin that handles everything — SSR, route scanning, code transforms, and the dev server:
45
+
46
+ ```typescript
47
+ import { defineConfig } from "vite";
48
+ import { veloPlugin } from "@mauroandre/velojs/vite";
49
+
50
+ export default defineConfig({
51
+ plugins: [veloPlugin()],
52
+ });
53
+ ```
54
+
55
+ That's it. No extra plugins needed.
56
+
57
+ ## package.json scripts
58
+
59
+ Your project needs three scripts:
60
+
61
+ ```json
62
+ {
63
+ "scripts": {
64
+ "dev": "velojs dev",
65
+ "build": "velojs build",
66
+ "start": "velojs start"
67
+ }
68
+ }
69
+ ```
70
+
71
+ - `dev` — starts the development server with hot reload
72
+ - `build` — compiles the client bundle and server entry for production
73
+ - `start` — runs the production server (automatically sets `NODE_ENV=production`)
74
+
75
+ ## app/client-root.tsx — The HTML shell
76
+
77
+ This is the outermost component of your app. It renders the `<html>`, `<head>`, and `<body>` tags. Every page in your app will be rendered inside this shell.
78
+
79
+ The `<Scripts />` component is required — it injects the CSS and JavaScript that your app needs (Vite HMR in dev, compiled assets in production).
80
+
81
+ ```tsx
82
+ import type { ComponentChildren } from "preact";
83
+ import { Scripts } from "@mauroandre/velojs";
84
+
85
+ export const Component = ({ children }: { children?: ComponentChildren }) => (
86
+ <html lang="en">
87
+ <head>
88
+ <meta charset="UTF-8" />
89
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
90
+ <title>My App</title>
91
+ <Scripts />
92
+ </head>
93
+ <body>{children}</body>
94
+ </html>
95
+ );
96
+ ```
97
+
98
+ ## app/client.tsx — Client entry
99
+
100
+ This file runs only on the client (browser). Use it to import global CSS or initialize client-side libraries:
101
+
102
+ ```typescript
103
+ import "./styles/global.css";
104
+ ```
105
+
106
+ ## app/server.tsx — Server entry
107
+
108
+ This file runs only on the server. Use it to connect to databases, register custom API routes, or start background jobs:
109
+
110
+ ```typescript
111
+ import { addRoutes } from "@mauroandre/velojs/server";
112
+ import type { Hono } from "hono";
113
+
114
+ // Register custom API routes
115
+ addRoutes((app: Hono) => {
116
+ app.get("/api/health", (c) => c.json({ ok: true }));
117
+ });
118
+ ```
119
+
120
+ ## app/routes.tsx — Route definitions
121
+
122
+ Routes are defined as a tree. Each node has a `module` (the page or layout component) and optionally `path`, `children`, and `middlewares`:
123
+
124
+ ```typescript
125
+ import type { AppRoutes } from "@mauroandre/velojs";
126
+ import * as Root from "./client-root.js";
127
+ import * as Home from "./pages/Home.js";
128
+
129
+ export default [
130
+ {
131
+ module: Root,
132
+ isRoot: true,
133
+ children: [
134
+ { path: "/", module: Home },
135
+ ],
136
+ },
137
+ ] satisfies AppRoutes;
138
+ ```
139
+
140
+ Page and layout modules **must** be imported with `import * as Name from "./x.js"`. This isn't style: when the plugin resolves each module's URL it only reads namespace imports, so a default or named import silently drops the route — the server logs `Module … has no fullPath` at startup and the page 404s. Use `./`-relative paths inside `appDirectory` too; a `../` path or a tsconfig alias produces a key the plugin can't match, with the same result.
141
+
142
+ ## Your first page
143
+
144
+ A page in VeloJS can export three things:
145
+
146
+ - `Component` — the Preact component that renders the UI
147
+ - `loader` — a server-side function that fetches data
148
+ - `action_*` — server-side functions callable from the client
149
+
150
+ Here's a simple page with a loader:
151
+
152
+ ```typescript
153
+ import type { LoaderArgs } from "@mauroandre/velojs";
154
+ import { useLoader } from "@mauroandre/velojs/hooks";
155
+
156
+ // This runs on the server. It fetches data for the page.
157
+ export const loader = async ({ c }: LoaderArgs) => {
158
+ return { message: "Hello, VeloJS!" };
159
+ };
160
+
161
+ // This runs on both server (SSR) and client (hydration).
162
+ export const Component = () => {
163
+ const { data } = useLoader<{ message: string }>();
164
+ return <h1>{data.value?.message}</h1>;
165
+ };
166
+ ```
167
+
168
+ The loader runs on the server, and the data is automatically available in the component through `useLoader()`. On the first page load (SSR), the data is injected into the HTML. On client-side navigation, it's fetched via an API call — all automatically.
169
+
170
+ ## Run the dev server
171
+
172
+ ```bash
173
+ npm run dev
174
+ ```
175
+
176
+ Open `http://localhost:3000` in your browser. Changes to your code will hot-reload instantly.
177
+
178
+ ## Configuration
179
+
180
+ The `veloPlugin()` accepts optional configuration:
181
+
182
+ ```typescript
183
+ veloPlugin({
184
+ appDirectory: "./app", // where your app files live (default: "./app")
185
+ routesFile: "routes.tsx", // your routes file (default: "routes.tsx")
186
+ serverInit: "server.tsx", // server init file (default: "server.tsx")
187
+ clientInit: "client.tsx", // client init file (default: "client.tsx")
188
+ });
189
+ ```
190
+
191
+ Most projects don't need to change these defaults.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: velojs-hooks
3
+ description: "Client/SSR hooks: `useLoader`, `useParams`, `useQuery`, `usePathname`, `useNavigate`, `touch`, `useEventStream`, `useSocket`. Use when reading URL params or query string, navigating programmatically, or updating signal state."
4
+ ---
5
+
6
+ # Hooks
7
+
8
+ VeloJS provides a set of hooks that work identically on both server (SSR) and client. On the server, they read from `AsyncLocalStorage` (isolated per request). On the client, they use the DOM and wouter-preact.
9
+
10
+ ## Available hooks
11
+
12
+ | Hook | Returns | Description |
13
+ |------|---------|-------------|
14
+ | `useLoader<T>(deps?)` | `{ data, loading, refetch }` | Page data with SSR + SPA support |
15
+ | `Loader<T>()` | `{ data, loading }` | Module-level SSR-only data |
16
+ | `useEventStream<T, S>(stream, opts?)` | `{ data, snapshot, closed, error }` | Real-time SSE stream subscription |
17
+ | `useParams<T>()` | `T` | URL parameters (`:id`, etc) |
18
+ | `useQuery<T>()` | `T` | Query string parameters |
19
+ | `useNavigate()` | `navigate(path)` | Programmatic navigation |
20
+ | `usePathname()` | `string` | Absolute URL pathname |
21
+ | `touch(signal)` | `void` | Force signal notification after mutation |
22
+
23
+ ## useParams
24
+
25
+ Reads URL parameters from the current route. For example, if your route path is `/users/:id`:
26
+
27
+ ```typescript
28
+ const params = useParams<{ id: string }>();
29
+ console.log(params.id); // "123" when visiting /users/123
30
+ ```
31
+
32
+ ## useQuery
33
+
34
+ Reads query string parameters from the URL. For example, visiting `/search?q=hello&page=2`:
35
+
36
+ ```typescript
37
+ const query = useQuery<{ q: string; page: string }>();
38
+ console.log(query.q); // "hello"
39
+ console.log(query.page); // "2"
40
+ ```
41
+
42
+ ## useNavigate
43
+
44
+ Returns a function for programmatic navigation (when you need to navigate from code, not from a link):
45
+
46
+ ```typescript
47
+ const navigate = useNavigate();
48
+
49
+ const handleLogin = async () => {
50
+ const result = await action_login({ body: credentials });
51
+ if (!result.error) {
52
+ navigate("/admin");
53
+ }
54
+ };
55
+ ```
56
+
57
+ ## usePathname
58
+
59
+ Returns the **absolute** pathname of the current URL, and works identically on the server (SSR) and the client. It reads the real URL rather than any router state, so you always get the full path.
60
+
61
+ ```typescript
62
+ const pathname = usePathname();
63
+ // Always returns the full path, e.g., "/admin/users/123"
64
+ ```
65
+
66
+ This is useful when you need the exact current URL — for active-link styling, breadcrumbs, analytics, and the like.
67
+
68
+ ## useEventStream
69
+
70
+ Subscribes to a server-sent event stream created with `createEventStream`. Returns reactive signals for the latest event, initial snapshot, close state, and errors.
71
+
72
+ ```typescript
73
+ const { data, snapshot, closed, error } = useEventStream(stream, {
74
+ channel: "deploy-123", // optional
75
+ enabled: true, // optional, default true
76
+ });
77
+
78
+ // data.value — latest event received from the server
79
+ // snapshot.value — initial state on connect (if configured)
80
+ // closed.value — true when server closed the stream (closeOn matched)
81
+ // error.value — parse or connection error, if any
82
+ ```
83
+
84
+ The hook handles `EventSource` lifecycle automatically: opens on mount, closes on unmount, re-opens when `channel` changes. See [Event Streams](/docs/event-streams) for the full guide.
85
+
86
+ ## touch
87
+
88
+ Preact signals are great for reactivity, but they have a limitation: mutating a nested property (like changing `items[0].checked`) doesn't trigger a signal update. The `touch` function forces the signal to notify its subscribers after a mutation:
89
+
90
+ ```typescript
91
+ import { useSignal } from "@preact/signals";
92
+ import { touch } from "@mauroandre/velojs/hooks";
93
+
94
+ const items = useSignal<Item[]>([]);
95
+
96
+ // Mutating a nested property — signal doesn't know about this
97
+ items.value[0].checked = true;
98
+
99
+ // touch() creates a shallow copy, which triggers the update
100
+ touch(items);
101
+ ```
102
+
103
+ Use `touch` whenever you mutate nested properties on a signal value instead of replacing the entire value.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: velojs-link-component
3
+ description: "The `<Link>` component for SPA navigation, linking by route module, and substituting `:params`. Use when adding navigation, a menu, or any internal link."
4
+ ---
5
+
6
+ # Link Component
7
+
8
+ The `Link` component handles navigation in VeloJS. It supports both simple string paths and type-safe module references.
9
+
10
+ All link paths are **root-absolute** — they resolve from the root of your app, exactly as the server does. There is no per-layout relative context to keep track of.
11
+
12
+ ## Basic usage
13
+
14
+ ```typescript
15
+ import { Link } from "@mauroandre/velojs";
16
+
17
+ // Simple string path
18
+ <Link to="/users">Users</Link>
19
+
20
+ // With URL parameters
21
+ <Link to="/users/123">View User</Link>
22
+ ```
23
+
24
+ ## Module references
25
+
26
+ Instead of hardcoding paths, you can pass a route module directly. The link uses the module's `fullPath`, so if the path changes in `routes.tsx`, your links update automatically:
27
+
28
+ ```typescript
29
+ import { Link } from "@mauroandre/velojs";
30
+ import * as UserPage from "./users/UserDetail.js";
31
+
32
+ // Navigates to the module's full path (e.g. /users/:id)
33
+ <Link to={UserPage} params={{ id: "123" }}>View</Link>
34
+ ```
35
+
36
+ ## Parameters and query strings
37
+
38
+ Use `params` to substitute `:param` placeholders in the path, and `search` to add query string parameters:
39
+
40
+ ```typescript
41
+ <Link to={UserPage} params={{ id: "123" }} search={{ tab: "settings" }}>
42
+ User Settings
43
+ </Link>
44
+ // Renders: /users/123?tab=settings
45
+ ```
46
+
47
+ ## The `~/` prefix
48
+
49
+ String paths may start with `~/`. This is wouter-preact's root-absolute prefix: the `~` is stripped and the path resolves from the root. Since VeloJS links are already root-absolute, `~/x` and `/x` resolve to the same place:
50
+
51
+ ```typescript
52
+ <Link to="~/stacks">Stacks</Link> // → /stacks
53
+ <Link to="/stacks">Stacks</Link> // → /stacks (identical)
54
+ ```
55
+
56
+ The prefix is supported for backward compatibility — you can keep existing `~/` links as-is, or drop the `~` in new code.
57
+
58
+ > **Migrating from older versions:** earlier releases resolved string and module links relative to the current layout's nest context, and `~/` escaped to the root. Layout nesting on the client now mirrors the server (full-path matching, no nested base), so every link is root-absolute. In practice: keep using absolute paths (`/stacks`) or module references; existing `~/` links keep working.
59
+
60
+ ## Props reference
61
+
62
+ | Prop | Type | Description |
63
+ |------|------|-------------|
64
+ | `to` | `string \| RouteModule` | Destination path or module. A string may start with `~/` (root-absolute); a module resolves to its `fullPath`. |
65
+ | `params` | `Record<string, string>` | Substitutes `:param` placeholders in the path. |
66
+ | `search` | `Record<string, string>` | Appends query string parameters to the URL. |
67
+ | `absolute` | `boolean` | Deprecated, no-op. Module references always use `fullPath`. Kept for backward compatibility. |
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: velojs-loaders
3
+ description: "Loading server data for a page: the `loader` export, `useLoader`, the `Loader` handle for sharing a layout's data with its children, hydration and SPA refetch. Use when a page needs data from a database or API before it renders."
4
+ ---
5
+
6
+ # Loaders
7
+
8
+ Loaders are how you fetch data on the server and make it available to your components. VeloJS provides two patterns, each designed for a different use case.
9
+
10
+ ## useLoader — Component-level data
11
+
12
+ Use `useLoader` inside a `Component` when you need data that's specific to that page. It supports both SSR (server-side rendering) and SPA navigation (client-side fetch).
13
+
14
+ ```typescript
15
+ export const Component = () => {
16
+ const { data, loading, refetch } = useLoader<MyType>();
17
+
18
+ // data: Signal<T | null> — the loader data (reactive)
19
+ // loading: Signal<boolean> — true while fetching
20
+ // refetch: () => void — manually re-fetch the data
21
+ };
22
+ ```
23
+
24
+ **How it works:**
25
+ - On the first page load (SSR), the data is rendered on the server and injected into the HTML
26
+ - When the user navigates to this page via a link (SPA), `useLoader` fetches the data from the server automatically
27
+ - The data is a Preact signal — your UI updates automatically when it changes
28
+
29
+ ### Re-fetching on route changes — automatic
30
+
31
+ If your page's route declares a param, you don't have to do anything. VeloJS reads the route tree, so it already knows that a module at `/users/:id` depends on `id`, and refreshes its data when you navigate from `/users/1` to `/users/2`:
32
+
33
+ ```typescript
34
+ // app/users/Detail.tsx — routed at /users/:id
35
+ export const Component = () => {
36
+ const { data } = useLoader<User>(); // refreshes on :id change, no deps needed
37
+ };
38
+ ```
39
+
40
+ ### Deps — for what the route doesn't declare
41
+
42
+ `routes.tsx` declares path params (`/users/:id`), never query strings or client state. When your data depends on one of those, pass it as a dependency:
43
+
44
+ ```typescript
45
+ const query = useQuery<{ tab: string }>();
46
+ const { data } = useLoader<Report>([query.tab]); // ?tab= is not in the route
47
+ ```
48
+
49
+ A dep that just repeats a path param is harmless but redundant — the framework already knows.
50
+
51
+ ## Loader — Module-level shared data
52
+
53
+ Use `Loader` (capital L, outside of components) when **another module needs your data**. It gives you a handle you can export — typically from a layout, so its child pages can read it.
54
+
55
+ ```typescript
56
+ // app/admin/Layout.tsx
57
+ import { Loader } from "@mauroandre/velojs/hooks";
58
+
59
+ export const loader = async ({ c }: LoaderArgs) => {
60
+ const user = c.get("user");
61
+ return { name: user.name, role: user.role };
62
+ };
63
+
64
+ // Runs at module import — not inside a component
65
+ export const { data: userData, refetch } = Loader<UserData>();
66
+
67
+ export const Component = ({ children }) => (
68
+ <div>
69
+ <header>Hello, {userData.value?.name}</header>
70
+ {children}
71
+ </div>
72
+ );
73
+ ```
74
+
75
+ Child pages import the handle directly:
76
+
77
+ ```typescript
78
+ // app/admin/Dashboard.tsx
79
+ import { userData } from "./Layout.js";
80
+
81
+ export const Component = () => (
82
+ <div>Role: {userData.value?.role}</div>
83
+ );
84
+ ```
85
+
86
+ `Loader` and `useLoader` for the same module return the **same signal**, so a layout's data and a child's read of it are one value. A child can update it after a mutation without a round-trip:
87
+
88
+ ```typescript
89
+ import { userData } from "./Layout.js";
90
+ import { touch } from "@mauroandre/velojs/hooks";
91
+
92
+ const handleSave = async () => {
93
+ const saved = await action_save({ body: { name: name.value } });
94
+ userData.value!.name = saved.name; // you already know the result
95
+ touch(userData); // notify — see the hooks doc
96
+ };
97
+ ```
98
+
99
+ Or let the server recompute, when the new state isn't something the client can derive:
100
+
101
+ ```typescript
102
+ import { refetch } from "./Layout.js";
103
+ await action_reset({ body: { id } });
104
+ refetch(); // e.g. aggregate counters
105
+ ```
106
+
107
+ > **Never mirror loader data into a module-level `export let`.** On the server, a module binding is per-process, not per-request — a value stored there belongs to whichever request wrote it last. `Loader`'s server value is an AsyncLocalStorage getter, so it is per-request by construction. That's the whole reason the handle is safe to export.
108
+
109
+ ## When to use which
110
+
111
+ | Scenario | Use |
112
+ |----------|-----|
113
+ | Page-specific data (list of users, post details) | `useLoader` |
114
+ | Data another module reads (a layout's list, the current record) | `Loader` |
115
+ | Data that changes when the route's params change | either — automatic |
116
+ | Data that depends on a query string or client state | `useLoader` with deps |
117
+ | Refresh after a mutation | `refetch()` (server recomputes) or `touch()` (you already know) |
118
+
119
+ ## How data flows
120
+
121
+ Understanding the data flow helps you debug issues:
122
+
123
+ **First page load (SSR):**
124
+ 1. All loaders in the route hierarchy run in parallel on the server
125
+ 2. The server injects every module's data into the HTML as `window.__PAGE_DATA__`, keyed by module
126
+ 3. `useLoader()` and `Loader()` read it — non-destructively, so any number of components can read the same module
127
+
128
+ **Client-side navigation (SPA):**
129
+ 1. User clicks a link
130
+ 2. VeloJS compares each module's route pattern against the new URL and marks stale the ones whose **declared params** changed
131
+ 3. One `?_data=1` request serves all of them
132
+ 4. Only the stale modules are written — a module the client owns locally (a filtered list, an optimistic edit) is left alone
133
+
134
+ That last point is why a layout at `/admin/empresas` keeps a filtered list while you navigate among its children: its route declares no param, so nothing invalidates it.
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: velojs-middlewares
3
+ description: "Route middleware: auth guards, redirects, per-route server logic, passing values via `c.set`/`c.get`. Use when protecting a route, requiring login, checking permissions, or running server logic before a page or action."
4
+ ---
5
+
6
+ # Middlewares
7
+
8
+ Middlewares are server-side functions that run before your loaders and actions. They're perfect for authentication, authorization, logging, and other cross-cutting concerns. The Vite plugin automatically removes them from the client bundle.
9
+
10
+ ## Creating a middleware
11
+
12
+ Use `createMiddleware` from `@mauroandre/velojs/factory`. It wraps Hono's middleware API:
13
+
14
+ ```typescript
15
+ // app/modules/auth/auth.middleware.ts
16
+ import { createMiddleware } from "@mauroandre/velojs/factory";
17
+ import { getCookie } from "@mauroandre/velojs/cookie";
18
+
19
+ export const authMiddleware = createMiddleware(async (c, next) => {
20
+ const token = getCookie(c, "session");
21
+
22
+ if (!token) {
23
+ // No token: redirect GET requests, return 401 for API calls
24
+ if (c.req.method === "GET") return c.redirect("/login");
25
+ return c.json({ error: "unauthorized" }, 401);
26
+ }
27
+
28
+ // Verify the token and attach user data to the context
29
+ const user = await verifyToken(token);
30
+ c.set("user", user);
31
+
32
+ // Continue to the next middleware or handler
33
+ await next();
34
+ });
35
+ ```
36
+
37
+ ## Using in routes
38
+
39
+ Add the `middlewares` property to any route node. All children of that node will inherit the middleware:
40
+
41
+ ```typescript
42
+ export default [
43
+ {
44
+ module: Root,
45
+ isRoot: true,
46
+ children: [
47
+ // Public routes — no middleware
48
+ {
49
+ module: AuthLayout,
50
+ children: [
51
+ { path: "/login", module: Login },
52
+ ],
53
+ },
54
+ // Protected routes — authMiddleware applies to everything below
55
+ {
56
+ module: AdminLayout,
57
+ middlewares: [authMiddleware],
58
+ children: [
59
+ { path: "/", module: Dashboard },
60
+ { path: "/stacks", module: Stacks },
61
+
62
+ // Admin-only routes — both middlewares apply
63
+ {
64
+ path: "/master",
65
+ module: MasterLayout,
66
+ middlewares: [masterMiddleware],
67
+ children: [
68
+ { path: "/workers", module: Workers },
69
+ { path: "/settings", module: Settings },
70
+ ],
71
+ },
72
+ ],
73
+ },
74
+ ],
75
+ },
76
+ ] satisfies AppRoutes;
77
+ ```
78
+
79
+ ## Inheritance
80
+
81
+ Middlewares **accumulate** from parent to child. In the example above:
82
+
83
+ - `/` and `/stacks` → only `authMiddleware` runs
84
+ - `/master/workers` and `/master/settings` → `authMiddleware` runs first, then `masterMiddleware`
85
+
86
+ ### What middlewares protect
87
+
88
+ A middleware on a layout guards **every request** that reaches a nested route, including:
89
+
90
+ | Request type | URL pattern | When it happens |
91
+ |--------------|-------------|-----------------|
92
+ | Page load | `GET /stacks` | User opens the page directly (first visit, refresh) |
93
+ | Data fetch | `GET /stacks?_data=1` | SPA navigation — `useLoader` fetches JSON |
94
+ | Action call | `POST /_action/admin/Stacks/delete` | User triggers a server action |
95
+
96
+ You don't need to add the middleware to each route manually — declaring it once on the layout covers all three paths automatically. This is especially important for authentication: without it, your API endpoints could be reached even if the page itself is protected.
97
+
98
+ ### Example: protecting an action via layout middleware
99
+
100
+ ```typescript
101
+ // app/routes.tsx
102
+ {
103
+ module: AdminLayout,
104
+ middlewares: [authMiddleware],
105
+ children: [
106
+ { path: "/stacks", module: Stacks },
107
+ ],
108
+ }
109
+ ```
110
+
111
+ ```typescript
112
+ // app/admin/Stacks.tsx
113
+ export const action_delete = async ({ body, c }: ActionArgs<{ id: string }>) => {
114
+ // authMiddleware already ran — user is guaranteed to be authenticated
115
+ const user = c!.get("user");
116
+ // ... delete logic
117
+ };
118
+ ```
119
+
120
+ When the client calls `action_delete({ body: { id: "abc" } })`, it hits `POST /_action/admin/Stacks/delete`. Hono runs `authMiddleware` before the action executes. If the middleware returns an error (e.g., 401), the action never runs.
121
+
122
+ ## Sharing data between middleware and handlers
123
+
124
+ Use Hono's `c.set()` in the middleware and `c.get()` in your loaders and actions:
125
+
126
+ ```typescript
127
+ // Middleware sets the user
128
+ c.set("user", { id: "123", name: "Mauro", role: "master" });
129
+
130
+ // Loader reads it
131
+ export const loader = async ({ c }: LoaderArgs) => {
132
+ const user = c.get("user");
133
+ return { greeting: `Hello, ${user.name}` };
134
+ };
135
+
136
+ // Action reads it
137
+ export const action_save = async ({ body, c }: ActionArgs<{ name: string }>) => {
138
+ const user = c!.get("user");
139
+ // user is available here
140
+ };
141
+ ```
142
+
143
+ This is the recommended pattern for passing authentication data (current user, permissions, etc) from middleware to your page code.