Server Functions
Server Functions let Client Components call async functions that execute on the server. Form actions also work without JavaScript.
"use server" marks a function so the client can call it over the network. The same function is a
Server Action when it's passed to a form's action prop or called from an action, following
React's terminology. See React's Server Functions docs
for the full model.
Defining Server Functions
Add "use server" at the top of a module to turn all its exports into Server Functions. Every
exported value in that module must be an async function:
// app/actions.ts
"use server"
export async function createItem(name: string) {
await db.items.create({ name })
}Or mark a single function inline in a Server Component and pass it to a Client Component:
export default function Page() {
async function greet(name: string) {
"use server"
return `Hello ${name}`
}
return <GreetingButton greet={greet} />
}Calling Server Functions
Call a Server Function from a Client Component like any async function. Rebask sends the call to the server and resolves with the result:
"use client"
import { createItem } from "./actions"
export function NewItemButton({ name }: { name: string }) {
return <button onClick={() => createItem(name)}>Create</button>
}In Client Components, call Server Functions from event handlers or effects, not during render.
Calling a Server Function from a Server Component runs it in the same process, with no network request.
Server Functions with form actions
Pass a Server Function to a form's action prop to run it on submit. Because the form submits
natively, this works without JavaScript:
"use client"
import { updateName } from "./actions"
export function NameForm() {
return (
<form action={updateName}>
<input name="name" />
<button type="submit">Save</button>
</form>
)
}Use useActionState to read the result back into the page:
"use client"
import { useActionState } from "react"
import { saveProfile } from "./actions"
export function ProfileForm() {
const [state, formAction] = useActionState(saveProfile, { error: null })
return (
<form action={formAction}>
<input name="displayName" />
{state.error ? <p>{state.error}</p> : null}
<button type="submit">Save</button>
</form>
)
}Define the action as (prevState, formData):
"use server"
export async function saveProfile(_prevState: unknown, formData: FormData) {
const name = String(formData.get("displayName") ?? "")
if (!name) return { error: "Name is required" }
await db.profiles.update(name)
return { error: null }
}Bind arguments that aren't part of the form:
<form action={updateItem.bind(null, itemId)}>Bound arguments come before the action's other arguments. The submitted FormData is always last.
Arguments
When called from a Client Component, arguments cross the network and must be serializable. Strings,
numbers, booleans, null, undefined, arrays, nested objects, Date, Map, Set, typed arrays,
ArrayBuffer, Blob, FormData (including File uploads), and React elements are supported. Custom
class instances and functions are not.
Form submissions pass the submitted FormData as the final argument:
"use server"
export async function updateItem(itemId: number, formData: FormData) {
const name = String(formData.get("name"))
await db.items.update(itemId, name)
}Request APIs
headers() and cookies()
are available inside a Server Function. Write response cookies with await cookies():
"use server"
import { cookies } from "@rebask/core/cookies"
export async function login() {
const store = await cookies()
store.set("session", token, { httpOnly: true, path: "/" })
}Cookie writes apply even if the function throws.
routeParams() and
searchParam() aren't available, because a function isn't
tied to a route.
Return values and errors
Return values sent to a Client Component must be serializable, and React elements are supported. Return a value for expected failures:
"use server"
export async function createProfile(formData: FormData) {
const name = String(formData.get("name") ?? "")
if (!name) {
return { error: "Name is required" }
}
await db.profiles.create({ name })
}Throwing is for unexpected errors. When you call a Server Function from a Client Component, the function rejects in the browser, so handle it where you call it:
<button
onClick={async () => {
try {
await createItem(name)
} catch (error) {
setError(getMessage(error))
}
}}
>
Create
</button>Errors thrown inside the function reject with a plain Error rebuilt from the serialized response,
so custom error classes don't survive. In development the message is the original one. In production
React replaces it with a minified error code, such as Minified React error #441, and Rebask logs
the original message on the server.
A throw in a no-JS form submission renders the framework error page.
Mutations
Server Functions do not refresh client route data. After a mutation, refresh the current client
segment with router.revalidate(). If the
mutation invalidates tagged server data, also call revalidateTag().
Security
Rebask checks the Origin header against the request host before running a Server Function.
Same-origin requests pass. Cross-origin requests are rejected unless the origin host matches
serverFunctions.allowedOrigins.
allowedOrigins accepts exact hosts and wildcard patterns: *.example.com matches one subdomain
level, and **.example.com matches any depth. Behind a proxy, add the forwarded hostname to
serverFunctions.allowedForwardedHosts so X-Forwarded-Host is trusted for the comparison.
Request bodies are limited to 1 MB by default. Larger requests get a 413. Set
bodySizeLimit in bytes to change it.