Modern full-stack React framework with SSR, streaming, server functions, and API routes powered by TanStack Router and Vite.
—
The RPC (Remote Procedure Call) system provides type-safe client-server communication with automatic serialization and request handling. It enables seamless function calls between client and server environments with full type safety and error handling.
Creates client-side RPC functions that can communicate with server-side endpoints.
/**
* Creates a client-side RPC function for server communication
* @param functionId - Unique identifier for the RPC function
* @param fetcher - Function that handles the actual HTTP request
* @returns ClientRpc instance for making server calls
*/
function createClientRpc(
functionId: string,
fetcher: (...args: any[]) => Promise<any>
): ClientRpc;
interface ClientRpc {
(...args: any[]): Promise<any>;
functionId: string;
url: string;
}Usage Examples:
import { createClientRpc } from "@tanstack/react-start/client-rpc";
// Basic RPC function
const getUserRpc = createClientRpc(
"getUser",
async (id: string) => {
const response = await fetch(`/api/functions/getUser`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id })
});
return response.json();
}
);
// Use in React component
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
useEffect(() => {
getUserRpc(userId).then(setUser);
}, [userId]);
return user ? <div>{user.name}</div> : <div>Loading...</div>;
}
// Advanced RPC with error handling
const createUserRpc = createClientRpc(
"createUser",
async (userData: { name: string; email: string }) => {
try {
const response = await fetch("/api/functions/createUser", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(userData)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
} catch (error) {
console.error("RPC call failed:", error);
throw error;
}
}
);Creates server-side RPC functions that can be called from client code.
/**
* Creates a server-side RPC function with split import functionality
* @param functionId - Unique identifier for the RPC function
* @param splitImportFn - Function that handles dynamic imports and execution
* @returns ServerRpc instance with URL and function metadata
*/
function createServerRpc(
functionId: string,
splitImportFn: (...args: any[]) => any
): ServerRpc;
interface ServerRpc {
(...args: any[]): any;
url: string;
functionId: string;
[TSS_SERVER_FUNCTION]: true;
}Usage Examples:
import { createServerRpc } from "@tanstack/react-start/server-rpc";
// Basic server RPC
const getUserServerRpc = createServerRpc(
"getUser",
async (id: string) => {
// Dynamic import for server-only code
const { db } = await import("./database");
return db.user.findUnique({ where: { id } });
}
);
// RPC with complex business logic
const processOrderServerRpc = createServerRpc(
"processOrder",
async (orderData: {
items: Array<{ id: string; quantity: number }>;
customerId: string;
paymentMethod: string;
}) => {
// Split imports for server-only dependencies
const [
{ db },
{ paymentProcessor },
{ inventoryManager },
{ emailService }
] = await Promise.all([
import("./database"),
import("./payment"),
import("./inventory"),
import("./email")
]);
// Process order with full server-side logic
const order = await db.order.create({
data: {
customerId: orderData.customerId,
items: {
create: orderData.items.map(item => ({
productId: item.id,
quantity: item.quantity
}))
}
}
});
// Process payment
const payment = await paymentProcessor.charge({
amount: order.total,
method: orderData.paymentMethod,
customerId: orderData.customerId
});
// Update inventory
await inventoryManager.decrementStock(orderData.items);
// Send confirmation email
await emailService.sendOrderConfirmation(orderData.customerId, order);
return { order, payment };
}
);Utilities for handling server function requests and responses.
/**
* Server function fetcher for handling RPC requests
* @param url - The URL to fetch from
* @param options - Fetch options including method and body
* @returns Promise resolving to the response
*/
interface ServerFnFetcher {
(url: string, options: ServerFnFetchOptions): Promise<Response>;
}
interface ServerFnFetchOptions {
method: string;
headers?: HeadersInit;
body?: BodyInit;
}The RPC system uses environment variables for configuration:
// Server configuration
process.env.TSS_APP_BASE = "/"; // Base path for the application
process.env.TSS_SERVER_FN_BASE = "api/functions"; // Base path for server functionsRPC functions automatically generate URLs based on configuration:
// Generated URL structure
const baseUrl = `${TSS_APP_BASE}/${TSS_SERVER_FN_BASE}/`;
const functionUrl = baseUrl + functionId;
// Example: /api/functions/getUserimport { createClientRpc } from "@tanstack/react-start/client-rpc";
// Authenticated RPC calls
const createAuthenticatedRpc = (functionId: string) =>
createClientRpc(functionId, async (...args) => {
const token = localStorage.getItem("auth-token");
const response = await fetch(`/api/functions/${functionId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
},
body: JSON.stringify(args)
});
if (response.status === 401) {
// Handle authentication error
window.location.href = "/login";
throw new Error("Authentication required");
}
return response.json();
});
const getUserRpc = createAuthenticatedRpc("getUser");
const updateUserRpc = createAuthenticatedRpc("updateUser");import { createClientRpc } from "@tanstack/react-start/client-rpc";
// Cached RPC calls
const cache = new Map();
const createCachedRpc = (functionId: string, cacheTTL = 5 * 60 * 1000) =>
createClientRpc(functionId, async (...args) => {
const cacheKey = `${functionId}:${JSON.stringify(args)}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < cacheTTL) {
return cached.data;
}
const response = await fetch(`/api/functions/${functionId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args)
});
const data = await response.json();
cache.set(cacheKey, { data, timestamp: Date.now() });
return data;
});
const getUserRpc = createCachedRpc("getUser", 10 * 60 * 1000); // 10 minute cacheimport { createServerRpc } from "@tanstack/react-start/server-rpc";
const validateUserInput = (data: unknown) => {
if (!data || typeof data !== "object") {
throw new Error("Invalid input");
}
const { name, email } = data as any;
if (!name || typeof name !== "string") {
throw new Error("Name is required");
}
if (!email || !email.includes("@")) {
throw new Error("Valid email is required");
}
return { name, email };
};
const createUserServerRpc = createServerRpc(
"createUser",
async (input: unknown) => {
// Validate input
const userData = validateUserInput(input);
// Import server dependencies
const { db } = await import("./database");
const { hashPassword } = await import("./crypto");
const { sendWelcomeEmail } = await import("./email");
// Create user with validated data
const user = await db.user.create({
data: {
name: userData.name,
email: userData.email,
passwordHash: await hashPassword("temporary")
}
});
// Send welcome email asynchronously
sendWelcomeEmail(user.email, user.name).catch(console.error);
return { user: { id: user.id, name: user.name, email: user.email } };
}
);import { createClientRpc } from "@tanstack/react-start/client-rpc";
const createRobustRpc = (functionId: string, maxRetries = 3) =>
createClientRpc(functionId, async (...args) => {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(`/api/functions/${functionId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(args)
});
if (!response.ok) {
if (response.status >= 500 && attempt < maxRetries) {
// Retry on server errors
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
continue;
}
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || `HTTP ${response.status}`);
}
return response.json();
} catch (error) {
lastError = error;
if (attempt === maxRetries) break;
// Exponential backoff for retries
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
throw lastError;
});
const getUserRpc = createRobustRpc("getUser");// RPC function types
interface ClientRpc {
(...args: any[]): Promise<any>;
functionId: string;
url: string;
}
interface ServerRpc {
(...args: any[]): any;
url: string;
functionId: string;
[TSS_SERVER_FUNCTION]: true;
}
// Server function marker
declare const TSS_SERVER_FUNCTION: unique symbol;
// Fetch options for server functions
interface ServerFnFetchOptions {
method: string;
headers?: HeadersInit;
body?: BodyInit;
}
// Server function fetcher type
interface ServerFnFetcher {
(url: string, options: ServerFnFetchOptions): Promise<Response>;
}
// RPC stream types
interface RscStream {
// React Server Component stream handling
read(): Promise<any>;
pipe(destination: any): any;
}
// Fetcher data types
interface FetcherData {
url: string;
method: string;
headers: Headers;
body?: any;
}
interface FetcherBaseOptions {
method?: string;
headers?: HeadersInit;
}
// Compiled fetcher types
interface CompiledFetcherFnOptions {
method: string;
headers?: HeadersInit;
body?: BodyInit;
}
interface CompiledFetcherFn {
(url: string, options: CompiledFetcherFnOptions): Promise<Response>;
}
interface Fetcher {
fn: CompiledFetcherFn;
options: CompiledFetcherFnOptions;
}
// Optional and required fetcher types
interface OptionalFetcher {
fetcher?: Fetcher;
}
interface RequiredFetcher {
fetcher: Fetcher;
}Install with Tessl CLI
npx tessl i tessl/npm-tanstack--react-start