Server Actions
Write a mutation as a server function, call it from the client, and revalidate the cache it affects — all type-checked, CSRF-protected, and served at one endpoint.
Defining an action
Wrap a server function with action(). Give it a stable id so the client can dispatch it, and optionally declare what to revalidate on success.
// src/actions/posts.js
import { action, revalidatePath } from 'what-framework/server';
import { createPost } from '../db.js';
export const createPostAction = action(
async ({ title, body }) => {
const post = createPost({ title, body });
return { ok: true, slug: post.slug };
},
{ id: 'createPost', revalidate: ['/'], revalidateTags: ['posts'] }
);
Serving actions
Actions are dispatched over POST /__what_action. The deploy adapters mount this for you; importing the action module (e.g. from your routes file) registers it. With a manual server you can mount it directly:
import { createActionHandler } from 'what-framework/server';
const handler = createActionHandler({ getCsrfToken });
Calling from the client
A progressively-enhanced form works with no JavaScript — it POSTs to the endpoint with a data-action attribute:
// src/pages/new-post.jsx — submits even before any client JS loads
export default function NewPost() {
return (
<form method="post" action="/__what_action" data-action="createPost">
<input name="title" required />
<textarea name="body" required></textarea>
<button>Publish</button>
</form>
);
}
Or call it imperatively and get the typed return value:
const res = await fetch('/__what_action', {
method: 'POST',
headers: { 'x-what-action': 'createPost', 'content-type': 'application/json' },
body: JSON.stringify({ args: [{ title, body }] }),
});
const { slug } = await res.json();
Revalidating after a mutation
The revalidate / revalidateTags options fire automatically after the action resolves, purging the origin ISR cache (and any CDN) so the next request re-renders with fresh data. You can also call them by hand inside the action:
import { revalidatePath, revalidateTag } from 'what-framework/server';
revalidatePath('/'); // purge one path
revalidateTag('posts'); // purge everything tagged 'posts'
Two revalidates, on purpose
Server revalidatePath/revalidateTag (cache) are distinct from the client router's invalidatePath (in-memory nav pub-sub). Server purges the rendered cache; the client one re-runs a client loader. See Caching & ISR.
CSRF & error masking
The handler validates a CSRF token by default (inject one via getCsrfToken; a meta tag is emitted into the document). It fails closed: a missing or bad token is rejected. Thrown errors are masked to a generic 500 so internal details never reach the client — the real error is logged server-side.