newX 1.3 — islands to disk, server-mode islands, image proxy

Server Functions

Server functions

Call server-side functions from the browser without writing REST endpoints. Server functions live in src/actions/. Import one into a client component and call it like a normal function, or call it manually with fetch. Both compile down to the same request.

Defining server functions

Create a file in src/actions/ and export named async functions. Each function receives a Request object and any arguments you pass.

src/actions/greet.ts
export async function greet(name: string) {  return `Hello, ${name}! The server time is ${new Date().toISOString()}.`;}export async function sendEmail({ to, subject, body }: {  to: string;  subject: string;  body: string;}) {  // send email logic  return { sent: true, to };}

Or register a map in one shot with export const actions — handy for grouping several functions under one file. This also works in src/api/ files and page files, so a greet.ts with no page component still registers its actions.

src/actions/greet.ts
export const actions = {  greet: async (name: string) => `Hello, ${name}!`,  ping: async () => ({ pong: true }),};

Calling actions directly

Import the function into a client component and call it like any other async function. When you run x build, the bundler swaps the import for a generated fetch client before it reaches the browser, so the real implementation, db calls and all, never gets bundled.

client component
"use client";import { useState } from "react";import { subscribeUser } from "../actions/subscribe";export default function SubscribeForm() {  const [status, setStatus] = useState("");  async function handleSubmit(e: React.FormEvent) {    e.preventDefault();    const form = new FormData(e.target as HTMLFormElement);    const email = form.get("email") as string;    await subscribeUser(email);    setStatus("Subscribed!");  }  return (    <form onSubmit={handleSubmit} className="space-y-4">      <input        name="email"        type="email"        placeholder="you@example.com"        className="rounded-xl border border-border bg-card px-4 py-2"      /> >      <button type="submit" className="rounded-xl bg-primary px-4 py-2 text-primary-foreground">        Subscribe      </button>      {status && <p className="text-muted-foreground">{status}</p>}    </form>  );}

Only files under actionsDir get this treatment. Import a regular server-only helper into client code and it bundles as-is; if it leaks a secret, the build-time env isolation check catches it instead.

Calling manually with fetch

This is what the direct-import style compiles down to, and it works the same way in dev and in production: a POST request to /__x/actions/<filename>/<functionName>. The arguments are sent as JSON in the request body.

client component
"use client";import { useState } from "react";export default function GreetForm() {  const [message, setMessage] = useState("");  async function handleSubmit(e: React.FormEvent) {    e.preventDefault();    const form = new FormData(e.target as HTMLFormElement);    const name = form.get("name");    const res = await fetch("/__x/actions/greet/greet", {      method: "POST",      headers: { "Content-Type": "application/json" },      body: JSON.stringify([name]),    });    const data = await res.text();    setMessage(data);  }  return (    <form onSubmit={handleSubmit} className="space-y-4">      <input        name="name"        placeholder="Enter your name"        className="rounded-xl border border-border bg-card px-4 py-2"      /> >      <button type="submit" className="rounded-xl bg-primary px-4 py-2 text-primary-foreground">        Greet me      </button>      {message && <p className="text-muted-foreground">{message}</p>}    </form>  );}

Reach for this style directly when you're calling an action from outside an island, or anywhere you'd rather see the request explicitly.

Server functions from loaders

You can also import and call server functions directly in loaders. No HTTP needed, since they share the same process.

src/pages/dashboard.tsx
import type { RouteProps, LoaderArgs } from "@thexjs/core";import { getDashboardData } from "../actions/dashboard";export async function loader({ request }: LoaderArgs) {  const data = await getDashboardData();  return { data };}export default function Dashboard({ loaderData }: RouteProps) {  return <div>...</div>;}

Use cases

Server functions are ideal for form handling, sending emails, database mutations, and any server-side logic that doesn't need a dedicated REST API. They reduce boilerplate and keep your client code simple.