Server Functions ermöglichen es Ihnen, Logik zu definieren, die fast überall (auch auf dem Client) aufgerufen werden kann, aber nur auf dem Server ausgeführt wird. Tatsächlich unterscheiden sie sich nicht wesentlich von API Routes, jedoch mit einigen wichtigen Unterschieden
Sie ähneln jedoch regulären API Routes insofern, als dass
Wie unterscheiden sich Server Functions von "React Server Functions"?
- TanStack Server Functions sind nicht an ein bestimmtes Frontend-Framework gebunden und können mit jedem Frontend-Framework oder auch ohne eines verwendet werden.
- TanStack Server Functions basieren auf Standard-HTTP-Anfragen und können beliebig oft aufgerufen werden, ohne unter serverseitigen Ausführungsengpässen zu leiden.
Server Functions können überall in Ihrer Anwendung definiert werden, müssen aber auf der obersten Ebene einer Datei definiert sein. Sie können in Ihrer gesamten Anwendung aufgerufen werden, einschließlich Loader, Hooks usw. Traditionell ist dieses Muster als Remote Procedure Call (RPC) bekannt, aber aufgrund der isomorphen Natur dieser Funktionen bezeichnen wir sie als Server Functions.
Server Functions können Middleware verwenden, um Logik, Kontext, gemeinsame Operationen, Voraussetzungen und vieles mehr zu teilen. Um mehr über die Middleware von Server Functions zu erfahren, lesen Sie bitte das Middleware-Handbuch.
Wir möchten dem tRPC-Team für die Inspiration zum Design der Server Functions von TanStack Start und für die Unterstützung bei der Implementierung danken. Wir lieben (und empfehlen) die Verwendung von tRPC für API Routes so sehr, dass wir darauf bestanden haben, dass Server Functions die gleiche erstklassige Behandlung und Entwicklererfahrung erhalten. Vielen Dank!
Server Functions werden mit der Funktion createServerFn aus dem Paket @tanstack/react-start definiert. Diese Funktion nimmt ein optionales options-Argument für die Angabe von Konfigurationen wie HTTP-Methode und Antworttyp entgegen und ermöglicht es Ihnen, von dem Ergebnis abzuleiten, um Dinge wie den Body der Server Function, Eingabevalidierung, Middleware usw. zu definieren. Hier ist ein einfaches Beispiel
// getServerTime.ts
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn().handler(async () => {
// Wait for 1 second
await new Promise((resolve) => setTimeout(resolve, 1000))
// Return the current time
return new Date().toISOString()
})
// getServerTime.ts
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn().handler(async () => {
// Wait for 1 second
await new Promise((resolve) => setTimeout(resolve, 1000))
// Return the current time
return new Date().toISOString()
})
Bei der Erstellung einer Server Function können Sie Konfigurationsoptionen angeben, um ihr Verhalten anzupassen
import { createServerFn } from '@tanstack/react-start'
export const getData = createServerFn({
method: 'GET', // HTTP method to use
response: 'data', // Response handling mode
}).handler(async () => {
// Function implementation
})
import { createServerFn } from '@tanstack/react-start'
export const getData = createServerFn({
method: 'GET', // HTTP method to use
response: 'data', // Response handling mode
}).handler(async () => {
// Function implementation
})
method
Gibt die HTTP-Methode für die Server Function-Anfrage an
method?: 'GET' | 'POST'
method?: 'GET' | 'POST'
Standardmäßig verwenden Server Functions GET, wenn nicht anders angegeben.
response
Steuert, wie Antworten verarbeitet und zurückgegeben werden
response?: 'data' | 'full' | 'raw'
response?: 'data' | 'full' | 'raw'
Server Functions akzeptieren einen einzelnen Parameter, der eine Vielzahl von Typen haben kann
Hier ist ein Beispiel für eine Server Function, die einen einfachen String-Parameter akzeptiert
import { createServerFn } from '@tanstack/react-start'
export const greet = createServerFn({
method: 'GET',
})
.validator((data: string) => data)
.handler(async (ctx) => {
return `Hello, ${ctx.data}!`
})
greet({
data: 'John',
})
import { createServerFn } from '@tanstack/react-start'
export const greet = createServerFn({
method: 'GET',
})
.validator((data: string) => data)
.handler(async (ctx) => {
return `Hello, ${ctx.data}!`
})
greet({
data: 'John',
})
Server Functions können so konfiguriert werden, dass sie ihre Eingabedaten zur Laufzeit validieren und gleichzeitig Typsicherheit hinzufügen. Dies ist nützlich, um sicherzustellen, dass die Eingabe den richtigen Typ hat, bevor die Server Function ausgeführt wird, und um freundlichere Fehlermeldungen bereitzustellen.
Dies geschieht mit der Methode validator. Sie akzeptiert jede Eingabe, die an die Server Function übergeben wird. Der Wert (und Typ), den Sie aus dieser Funktion zurückgeben, wird die Eingabe sein, die an den eigentlichen Handler der Server Function übergeben wird.
Validatoren integrieren sich auch nahtlos in externe Validatoren, wenn Sie etwas wie Zod verwenden möchten.
Hier ist ein einfaches Beispiel für eine Server Function, die den Eingabeparameter validiert
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
}
export const greet = createServerFn({ method: 'GET' })
.validator((person: unknown): Person => {
if (typeof person !== 'object' || person === null) {
throw new Error('Person must be an object')
}
if ('name' in person && typeof person.name !== 'string') {
throw new Error('Person.name must be a string')
}
return person as Person
})
.handler(async ({ data }) => {
return `Hello, ${data.name}!`
})
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
}
export const greet = createServerFn({ method: 'GET' })
.validator((person: unknown): Person => {
if (typeof person !== 'object' || person === null) {
throw new Error('Person must be an object')
}
if ('name' in person && typeof person.name !== 'string') {
throw new Error('Person.name must be a string')
}
return person as Person
})
.handler(async ({ data }) => {
return `Hello, ${data.name}!`
})
Validierungsbibliotheken wie Zod können wie folgt verwendet werden
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const Person = z.object({
name: z.string(),
})
export const greet = createServerFn({ method: 'GET' })
.validator((person: unknown) => {
return Person.parse(person)
})
.handler(async (ctx) => {
return `Hello, ${ctx.data.name}!`
})
greet({
data: {
name: 'John',
},
})
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const Person = z.object({
name: z.string(),
})
export const greet = createServerFn({ method: 'GET' })
.validator((person: unknown) => {
return Person.parse(person)
})
.handler(async (ctx) => {
return `Hello, ${ctx.data.name}!`
})
greet({
data: {
name: 'John',
},
})
Da Server Functions die Netzwerkgrenze überschreiten, ist es wichtig sicherzustellen, dass die an sie übergebenen Daten nicht nur den richtigen Typ haben, sondern auch zur Laufzeit validiert werden. Dies ist besonders wichtig bei der Verarbeitung von Benutzereingaben, da diese unvorhersehbar sein können. Um Entwickler zu ermutigen, ihre I/O-Daten zu validieren, sind Typen von der Validierung abhängig. Der Rückgabetyp der validator-Funktion ist die Eingabe für den Handler der Server Function.
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
}
export const greet = createServerFn({ method: 'GET' })
.validator((person: unknown): Person => {
if (typeof person !== 'object' || person === null) {
throw new Error('Person must be an object')
}
if ('name' in person && typeof person.name !== 'string') {
throw new Error('Person.name must be a string')
}
return person as Person
})
.handler(
async ({
data, // Person
}) => {
return `Hello, ${data.name}!`
},
)
function test() {
greet({ data: { name: 'John' } }) // OK
greet({ data: { name: 123 } }) // Error: Argument of type '{ name: number; }' is not assignable to parameter of type 'Person'.
}
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
}
export const greet = createServerFn({ method: 'GET' })
.validator((person: unknown): Person => {
if (typeof person !== 'object' || person === null) {
throw new Error('Person must be an object')
}
if ('name' in person && typeof person.name !== 'string') {
throw new Error('Person.name must be a string')
}
return person as Person
})
.handler(
async ({
data, // Person
}) => {
return `Hello, ${data.name}!`
},
)
function test() {
greet({ data: { name: 'John' } }) // OK
greet({ data: { name: 123 } }) // Error: Argument of type '{ name: number; }' is not assignable to parameter of type 'Person'.
}
Server Functions inferieren ihre Eingabe- und Ausgabetypen basierend auf der Eingabe des validator und dem Rückgabewert der handler-Funktionen. Tatsächlich kann der von Ihnen definierte validator eigene separate Eingabe-/Ausgabetypen haben, was nützlich sein kann, wenn Ihr Validator Transformationen an den Eingabedaten durchführt.
Um dies zu veranschaulichen, werfen wir einen Blick auf ein Beispiel mit der zod-Validierungsbibliothek
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const transactionSchema = z.object({
amount: z.string().transform((val) => parseInt(val, 10)),
})
const createTransaction = createServerFn()
.validator(transactionSchema)
.handler(({ data }) => {
return data.amount // Returns a number
})
createTransaction({
data: {
amount: '123', // Accepts a string
},
})
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const transactionSchema = z.object({
amount: z.string().transform((val) => parseInt(val, 10)),
})
const createTransaction = createServerFn()
.validator(transactionSchema)
.handler(({ data }) => {
return data.amount // Returns a number
})
createTransaction({
data: {
amount: '123', // Accepts a string
},
})
Obwohl wir dringend empfehlen, eine Validierungsbibliothek zur Validierung Ihrer Netz-I/O-Daten zu verwenden, möchten Sie vielleicht aus irgendeinem Grund Ihre Daten nicht validieren, aber dennoch Typsicherheit haben. Um dies zu tun, stellen Sie Typinformationen für die Server Function über eine Identitätsfunktion als validator bereit, die die Eingabe und/oder Ausgabe auf die richtigen Typen typisiert
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
}
export const greet = createServerFn({ method: 'GET' })
.validator((d: Person) => d)
.handler(async (ctx) => {
return `Hello, ${ctx.data.name}!`
})
greet({
data: {
name: 'John',
},
})
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
}
export const greet = createServerFn({ method: 'GET' })
.validator((d: Person) => d)
.handler(async (ctx) => {
return `Hello, ${ctx.data.name}!`
})
greet({
data: {
name: 'John',
},
})
Server Functions können JSON-serialisierbare Objekte als Parameter akzeptieren. Dies ist nützlich für die Übergabe komplexer Datenstrukturen an den Server
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
age: number
}
export const greet = createServerFn({ method: 'GET' })
.validator((data: Person) => data)
.handler(async ({ data }) => {
return `Hello, ${data.name}! You are ${data.age} years old.`
})
greet({
data: {
name: 'John',
age: 34,
},
})
import { createServerFn } from '@tanstack/react-start'
type Person = {
name: string
age: number
}
export const greet = createServerFn({ method: 'GET' })
.validator((data: Person) => data)
.handler(async ({ data }) => {
return `Hello, ${data.name}! You are ${data.age} years old.`
})
greet({
data: {
name: 'John',
age: 34,
},
})
Server Functions können FormData-Objekte als Parameter akzeptieren
import { createServerFn } from '@tanstack/react-start'
export const greetUser = createServerFn({ method: 'POST' })
.validator((data) => {
if (!(data instanceof FormData)) {
throw new Error('Invalid form data')
}
const name = data.get('name')
const age = data.get('age')
if (!name || !age) {
throw new Error('Name and age are required')
}
return {
name: name.toString(),
age: parseInt(age.toString(), 10),
}
})
.handler(async ({ data: { name, age } }) => {
return `Hello, ${name}! You are ${age} years old.`
})
// Usage
function Test() {
return (
<form
onSubmit={async (event) => {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const response = await greetUser({ data: formData })
console.log(response)
}}
>
<input name="name" />
<input name="age" />
<button type="submit">Submit</button>
</form>
)
}
import { createServerFn } from '@tanstack/react-start'
export const greetUser = createServerFn({ method: 'POST' })
.validator((data) => {
if (!(data instanceof FormData)) {
throw new Error('Invalid form data')
}
const name = data.get('name')
const age = data.get('age')
if (!name || !age) {
throw new Error('Name and age are required')
}
return {
name: name.toString(),
age: parseInt(age.toString(), 10),
}
})
.handler(async ({ data: { name, age } }) => {
return `Hello, ${name}! You are ${age} years old.`
})
// Usage
function Test() {
return (
<form
onSubmit={async (event) => {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const response = await greetUser({ data: formData })
console.log(response)
}}
>
<input name="name" />
<input name="age" />
<button type="submit">Submit</button>
</form>
)
}
Zusätzlich zu dem einzelnen Parameter, den Server Functions akzeptieren, können Sie auch auf den serverseitigen Request-Kontext innerhalb jeder Server Function zugreifen, indem Sie Hilfsmittel aus @tanstack/react-start/server verwenden. Unter der Haube verwenden wir das h3-Paket von Unjs, um plattformübergreifende HTTP-Anfragen durchzuführen.
Es gibt viele Kontextfunktionen, die Ihnen zur Verfügung stehen, z. B. für
Eine vollständige Liste der verfügbaren Kontextfunktionen finden Sie unter allen verfügbaren h3-Methoden oder inspizieren Sie den @tanstack/start-server-core-Quellcode.
Für den Anfang hier ein paar Beispiele
Verwenden wir die Funktion getWebRequest, um auf den Request selbst innerhalb einer Server Function zuzugreifen
import { createServerFn } from '@tanstack/react-start'
import { getWebRequest } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
const request = getWebRequest()
console.log(request.method) // GET
console.log(request.headers.get('User-Agent')) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
},
)
import { createServerFn } from '@tanstack/react-start'
import { getWebRequest } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
const request = getWebRequest()
console.log(request.method) // GET
console.log(request.headers.get('User-Agent')) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
},
)
Verwenden Sie die Funktion getHeaders, um innerhalb einer Server Function auf alle Header zuzugreifen
import { createServerFn } from '@tanstack/react-start'
import { getHeaders } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
console.log(getHeaders())
// {
// "accept": "*/*",
// "accept-encoding": "gzip, deflate, br",
// "accept-language": "en-US,en;q=0.9",
// "connection": "keep-alive",
// "host": "localhost:3000",
// ...
// }
},
)
import { createServerFn } from '@tanstack/react-start'
import { getHeaders } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
console.log(getHeaders())
// {
// "accept": "*/*",
// "accept-encoding": "gzip, deflate, br",
// "accept-language": "en-US,en;q=0.9",
// "connection": "keep-alive",
// "host": "localhost:3000",
// ...
// }
},
)
Sie können auch auf einzelne Header über die Funktion getHeader zugreifen
import { createServerFn } from '@tanstack/react-start'
import { getHeader } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
console.log(getHeader('User-Agent')) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
},
)
import { createServerFn } from '@tanstack/react-start'
import { getHeader } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
console.log(getHeader('User-Agent')) // Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3
},
)
Server Functions können verschiedene Arten von Rückgabewerten haben
Um beliebige primitive oder JSON-serialisierbare Objekte zurückzugeben, geben Sie einfach den Wert aus der Server Function zurück
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
return new Date().toISOString()
},
)
export const getServerData = createServerFn({ method: 'GET' }).handler(
async () => {
return {
message: 'Hello, World!',
}
},
)
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
return new Date().toISOString()
},
)
export const getServerData = createServerFn({ method: 'GET' }).handler(
async () => {
return {
message: 'Hello, World!',
}
},
)
Standardmäßig gehen Server Functions davon aus, dass jedes nicht-Response-Objekt entweder ein primitives oder ein JSON-serialisierbares Objekt ist.
Um mit benutzerdefinierten Headern zu antworten, können Sie die Funktion setHeader verwenden
import { createServerFn } from '@tanstack/react-start'
import { setHeader } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
setHeader('X-Custom-Header', 'value')
return new Date().toISOString()
},
)
import { createServerFn } from '@tanstack/react-start'
import { setHeader } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
setHeader('X-Custom-Header', 'value')
return new Date().toISOString()
},
)
Um mit einem benutzerdefinierten Statuscode zu antworten, können Sie die Funktion setResponseStatus verwenden
import { createServerFn } from '@tanstack/react-start'
import { setResponseStatus } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
setResponseStatus(201)
return new Date().toISOString()
},
)
import { createServerFn } from '@tanstack/react-start'
import { setResponseStatus } from '@tanstack/react-start/server'
export const getServerTime = createServerFn({ method: 'GET' }).handler(
async () => {
setResponseStatus(201)
return new Date().toISOString()
},
)
Um ein rohes Response-Objekt zurückzugeben, geben Sie ein Response-Objekt aus der Server Function zurück und setzen response: 'raw'
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn({
method: 'GET',
response: 'raw',
}).handler(async () => {
// Read a file from s3
return fetch('https://example.com/time.txt')
})
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn({
method: 'GET',
response: 'raw',
}).handler(async () => {
// Read a file from s3
return fetch('https://example.com/time.txt')
})
Die Option response: 'raw' ermöglicht auch Streaming-Antworten und andere Dinge
import { createServerFn } from '@tanstack/react-start'
export const streamEvents = createServerFn({
method: 'GET',
response: 'raw',
}).handler(async ({ signal }) => {
// Create a ReadableStream to send chunks of data
const stream = new ReadableStream({
async start(controller) {
// Send initial response immediately
controller.enqueue(new TextEncoder().encode('Connection established\n'))
let count = 0
const interval = setInterval(() => {
// Check if the client disconnected
if (signal.aborted) {
clearInterval(interval)
controller.close()
return
}
// Send a data chunk
controller.enqueue(
new TextEncoder().encode(
`Event ${++count}: ${new Date().toISOString()}\n`,
),
)
// End after 10 events
if (count >= 10) {
clearInterval(interval)
controller.close()
}
}, 1000)
// Ensure we clean up if the request is aborted
signal.addEventListener('abort', () => {
clearInterval(interval)
controller.close()
})
},
})
// Return a streaming response
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
})
import { createServerFn } from '@tanstack/react-start'
export const streamEvents = createServerFn({
method: 'GET',
response: 'raw',
}).handler(async ({ signal }) => {
// Create a ReadableStream to send chunks of data
const stream = new ReadableStream({
async start(controller) {
// Send initial response immediately
controller.enqueue(new TextEncoder().encode('Connection established\n'))
let count = 0
const interval = setInterval(() => {
// Check if the client disconnected
if (signal.aborted) {
clearInterval(interval)
controller.close()
return
}
// Send a data chunk
controller.enqueue(
new TextEncoder().encode(
`Event ${++count}: ${new Date().toISOString()}\n`,
),
)
// End after 10 events
if (count >= 10) {
clearInterval(interval)
controller.close()
}
}, 1000)
// Ensure we clean up if the request is aborted
signal.addEventListener('abort', () => {
clearInterval(interval)
controller.close()
})
},
})
// Return a streaming response
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive',
},
})
})
Die Option response: 'raw' ist besonders nützlich für
Neben speziellen redirect- und notFound-Fehlern können Server Functions beliebige benutzerdefinierte Fehler auslösen. Diese Fehler werden serialisiert und als JSON-Antwort mit einem 500er Statuscode an den Client gesendet.
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
throw new Error('Something went wrong!')
})
// Usage
function Test() {
try {
await doStuff()
} catch (error) {
console.error(error)
// {
// message: "Something went wrong!",
// stack: "Error: Something went wrong!\n at doStuff (file:///path/to/file.ts:3:3)"
// }
}
}
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
throw new Error('Something went wrong!')
})
// Usage
function Test() {
try {
await doStuff()
} catch (error) {
console.error(error)
// {
// message: "Something went wrong!",
// stack: "Error: Something went wrong!\n at doStuff (file:///path/to/file.ts:3:3)"
// }
}
}
Auf dem Client können Server Function-Aufrufe über ein AbortSignal abgebrochen werden. Auf dem Server benachrichtigt ein AbortSignal, wenn die Anfrage vor Abschluss der Ausführung geschlossen wurde.
import { createServerFn } from '@tanstack/react-start'
export const abortableServerFn = createServerFn().handler(
async ({ signal }) => {
return new Promise<string>((resolve, reject) => {
if (signal.aborted) {
return reject(new Error('Aborted before start'))
}
const timerId = setTimeout(() => {
console.log('server function finished')
resolve('server function result')
}, 1000)
const onAbort = () => {
clearTimeout(timerId)
console.log('server function aborted')
reject(new Error('Aborted'))
}
signal.addEventListener('abort', onAbort, { once: true })
})
},
)
// Usage
function Test() {
const controller = new AbortController()
const serverFnPromise = abortableServerFn({
signal: controller.signal,
})
await new Promise((resolve) => setTimeout(resolve, 500))
controller.abort()
try {
const serverFnResult = await serverFnPromise
console.log(serverFnResult) // should never get here
} catch (error) {
console.error(error) // "signal is aborted without reason"
}
}
import { createServerFn } from '@tanstack/react-start'
export const abortableServerFn = createServerFn().handler(
async ({ signal }) => {
return new Promise<string>((resolve, reject) => {
if (signal.aborted) {
return reject(new Error('Aborted before start'))
}
const timerId = setTimeout(() => {
console.log('server function finished')
resolve('server function result')
}, 1000)
const onAbort = () => {
clearTimeout(timerId)
console.log('server function aborted')
reject(new Error('Aborted'))
}
signal.addEventListener('abort', onAbort, { once: true })
})
},
)
// Usage
function Test() {
const controller = new AbortController()
const serverFnPromise = abortableServerFn({
signal: controller.signal,
})
await new Promise((resolve) => setTimeout(resolve, 500))
controller.abort()
try {
const serverFnResult = await serverFnPromise
console.log(serverFnResult) // should never get here
} catch (error) {
console.error(error) // "signal is aborted without reason"
}
}
Server Functions können normal aus Route-loadern, beforeLoads oder anderen vom Router gesteuerten APIs aufgerufen werden. Diese APIs sind dafür ausgestattet, Fehler, Weiterleitungen und NotFounds, die von Server Functions ausgelöst werden, automatisch zu behandeln.
import { getServerTime } from './getServerTime'
export const Route = createFileRoute('/time')({
loader: async () => {
const time = await getServerTime()
return {
time,
}
},
})
import { getServerTime } from './getServerTime'
export const Route = createFileRoute('/time')({
loader: async () => {
const time = await getServerTime()
return {
time,
}
},
})
Server Functions können redirect- oder notFound-Fehler auslösen, und obwohl nicht zwingend erforderlich, wird empfohlen, diese Fehler abzufangen und entsprechend zu behandeln. Um dies zu erleichtern, exportiert das Paket @tanstack/react-start einen Hook namens useServerFn, der verwendet werden kann, um Server Functions an Komponenten und Hooks zu binden
import { useServerFn } from '@tanstack/react-start'
import { useQuery } from '@tanstack/react-query'
import { getServerTime } from './getServerTime'
export function Time() {
const getTime = useServerFn(getServerTime)
const timeQuery = useQuery({
queryKey: 'time',
queryFn: () => getTime(),
})
}
import { useServerFn } from '@tanstack/react-start'
import { useQuery } from '@tanstack/react-query'
import { getServerTime } from './getServerTime'
export function Time() {
const getTime = useServerFn(getServerTime)
const timeQuery = useQuery({
queryKey: 'time',
queryFn: () => getTime(),
})
}
Bei der Verwendung von Server Functions beachten Sie, dass Weiterleitungen und NotFounds, die sie auslösen, nur automatisch behandelt werden, wenn sie aufgerufen werden von
Für andere Einsatzorte müssen Sie diese Fälle manuell behandeln.
Server Functions können einen redirect-Fehler auslösen, um den Benutzer zu einer anderen URL weiterzuleiten. Dies ist nützlich für die Behandlung von Authentifizierung, Autorisierung oder anderen Szenarien, bei denen Sie den Benutzer auf eine andere Seite weiterleiten müssen.
Um eine Weiterleitung auszulösen, können Sie die Funktion redirect verwenden, die aus dem Paket @tanstack/react-router exportiert wird
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the home page
throw redirect({
to: '/',
})
})
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the home page
throw redirect({
to: '/',
})
})
Weiterleitungen können alle gleichen Optionen wie router.navigate, useNavigate() und <Link>-Komponenten nutzen. Fühlen Sie sich also frei, auch Folgendes zu übergeben
Weiterleitungen können auch den Statuscode der Antwort festlegen, indem sie eine status-Option übergeben
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the home page with a 301 status code
throw redirect({
to: '/',
status: 301,
})
})
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the home page with a 301 status code
throw redirect({
to: '/',
status: 301,
})
})
Sie können auch zu einem externen Ziel weiterleiten, indem Sie href verwenden
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const auth = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the auth provider
throw redirect({
href: 'https://authprovider.com/login',
})
})
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const auth = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the auth provider
throw redirect({
href: 'https://authprovider.com/login',
})
})
⚠️ Verwenden Sie nicht die Funktion sendRedirect von @tanstack/react-start/server, um Soft-Redirects aus Server Functions zu senden. Dies sendet die Weiterleitung über den Location-Header und erzwingt eine vollständige Seiten-Hard-Navigation auf dem Client.
Sie können auch benutzerdefinierte Header bei einer Weiterleitung festlegen, indem Sie eine headers-Option übergeben
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the home page with a custom header
throw redirect({
to: '/',
headers: {
'X-Custom-Header': 'value',
},
})
})
import { redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Redirect the user to the home page with a custom header
throw redirect({
to: '/',
headers: {
'X-Custom-Header': 'value',
},
})
})
Beim Aufruf einer Server Function von einem loader oder beforeLoad-Route-Lifecycle kann ein spezieller notFound-Fehler ausgelöst werden, um dem Router anzuzeigen, dass die angeforderte Ressource nicht gefunden wurde. Dies ist nützlicher als ein einfacher 404-Statuscode, da es Ihnen ermöglicht, eine benutzerdefinierte 404-Seite zu rendern oder den Fehler auf benutzerdefinierte Weise zu behandeln. Wenn `notFound` aus einer Server Function außerhalb eines Route-Lifecycles ausgelöst wird, wird er nicht automatisch behandelt.
Um `notFound` auszulösen, können Sie die Funktion notFound verwenden, die aus dem Paket @tanstack/react-router exportiert wird
import { notFound } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const getStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Randomly return a not found error
if (Math.random() < 0.5) {
throw notFound()
}
// Or return some stuff
return {
stuff: 'stuff',
}
})
export const Route = createFileRoute('/stuff')({
loader: async () => {
const stuff = await getStuff()
return {
stuff,
}
},
})
import { notFound } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const getStuff = createServerFn({ method: 'GET' }).handler(async () => {
// Randomly return a not found error
if (Math.random() < 0.5) {
throw notFound()
}
// Or return some stuff
return {
stuff: 'stuff',
}
})
export const Route = createFileRoute('/stuff')({
loader: async () => {
const stuff = await getStuff()
return {
stuff,
}
},
})
Not Found-Fehler sind ein Kernfeature von TanStack Router,
Wenn eine Server Function einen (Nicht-Weiterleitungs-/Nicht-NotFound-)Fehler auslöst, wird dieser serialisiert und als JSON-Antwort mit einem 500er Statuscode an den Client gesendet. Dies ist nützlich für die Fehlersuche, aber Sie möchten diese Fehler möglicherweise benutzerfreundlicher behandeln. Sie können dies tun, indem Sie den Fehler abfangen und ihn in Ihrem Route-Lifecycle, Ihrer Komponente oder Ihrem Hook behandeln, wie Sie es normalerweise tun würden.
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
undefined.foo()
})
export const Route = createFileRoute('/stuff')({
loader: async () => {
try {
await doStuff()
} catch (error) {
// Handle the error:
// error === {
// message: "Cannot read property 'foo' of undefined",
// stack: "TypeError: Cannot read property 'foo' of undefined\n at doStuff (file:///path/to/file.ts:3:3)"
}
},
})
import { createServerFn } from '@tanstack/react-start'
export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
undefined.foo()
})
export const Route = createFileRoute('/stuff')({
loader: async () => {
try {
await doStuff()
} catch (error) {
// Handle the error:
// error === {
// message: "Cannot read property 'foo' of undefined",
// stack: "TypeError: Cannot read property 'foo' of undefined\n at doStuff (file:///path/to/file.ts:3:3)"
}
},
})
Ohne aktiviertes JavaScript gibt es nur eine Möglichkeit, Server Functions auszuführen: durch das Absenden eines Formulars.
Dies geschieht durch Hinzufügen eines form-Elements zur Seite mit dem HTML-Attribut action.
Beachten Sie, dass wir das HTML-Attribut action erwähnt haben. Dieses Attribut akzeptiert in HTML nur einen String, genau wie alle anderen Attribute.
Während React 19 Unterstützung für die Übergabe einer Funktion an action hinzugefügt hat, ist dies eine React-spezifische Funktion und kein Teil des HTML-Standards.
Das Attribut action teilt dem Browser mit, wohin die Formulardaten gesendet werden sollen, wenn das Formular abgesendet wird. In diesem Fall möchten wir die Formulardaten an die Server Function senden.
Um dies zu tun, können wir die url-Eigenschaft der Server Function nutzen
const yourFn = createServerFn({ method: 'POST' })
.validator((formData) => {
if (!(formData instanceof FormData)) {
throw new Error('Invalid form data')
}
const name = formData.get('name')
if (!name) {
throw new Error('Name is required')
}
return name
})
.handler(async ({ data: name }) => {
console.log(name) // 'John'
})
console.info(yourFn.url)
const yourFn = createServerFn({ method: 'POST' })
.validator((formData) => {
if (!(formData instanceof FormData)) {
throw new Error('Invalid form data')
}
const name = formData.get('name')
if (!name) {
throw new Error('Name is required')
}
return name
})
.handler(async ({ data: name }) => {
console.log(name) // 'John'
})
console.info(yourFn.url)
Und dies an das Attribut action des Formulars übergeben
function Component() {
return (
<form action={yourFn.url} method="POST">
<input name="name" defaultValue="John" />
<button type="submit">Click me!</button>
</form>
)
}
function Component() {
return (
<form action={yourFn.url} method="POST">
<input name="name" defaultValue="John" />
<button type="submit">Click me!</button>
</form>
)
}
Wenn das Formular abgesendet wird, wird die Server Function ausgeführt.
Um Argumente an eine Server Function zu übergeben, wenn ein Formular abgesendet wird, können Sie das input-Element mit dem name-Attribut verwenden, um das Argument an das FormData anzuhängen, das an Ihre Server Function übergeben wird
const yourFn = createServerFn({ method: 'POST' })
.validator((formData) => {
if (!(formData instanceof FormData)) {
throw new Error('Invalid form data')
}
const age = formData.get('age')
if (!age) {
throw new Error('age is required')
}
return age.toString()
})
.handler(async ({ data: formData }) => {
// `age` will be '123'
const age = formData.get('age')
// ...
})
function Component() {
return (
// We need to tell the server that our data type is `multipart/form-data` by setting the `encType` attribute on the form.
<form action={yourFn.url} method="POST" encType="multipart/form-data">
<input name="age" defaultValue="34" />
<button type="submit">Click me!</button>
</form>
)
}
const yourFn = createServerFn({ method: 'POST' })
.validator((formData) => {
if (!(formData instanceof FormData)) {
throw new Error('Invalid form data')
}
const age = formData.get('age')
if (!age) {
throw new Error('age is required')
}
return age.toString()
})
.handler(async ({ data: formData }) => {
// `age` will be '123'
const age = formData.get('age')
// ...
})
function Component() {
return (
// We need to tell the server that our data type is `multipart/form-data` by setting the `encType` attribute on the form.
<form action={yourFn.url} method="POST" encType="multipart/form-data">
<input name="age" defaultValue="34" />
<button type="submit">Click me!</button>
</form>
)
}
Wenn das Formular abgesendet wird, wird die Server Function mit den Daten des Formulars als Argument ausgeführt.
Unabhängig davon, ob JavaScript aktiviert ist, gibt die Server Function eine Antwort auf die vom Client getätigte HTTP-Anfrage zurück.
Wenn JavaScript aktiviert ist, kann diese Antwort als Rückgabewert der Server Function im JavaScript-Code des Clients abgerufen werden.
const yourFn = createServerFn().handler(async () => {
return 'Hello, world!'
})
// `.then` is not available when JavaScript is disabled
yourFn().then(console.log)
const yourFn = createServerFn().handler(async () => {
return 'Hello, world!'
})
// `.then` is not available when JavaScript is disabled
yourFn().then(console.log)
Wenn JavaScript jedoch deaktiviert ist, gibt es keine Möglichkeit, den Rückgabewert der Server Function im JavaScript-Code des Clients abzurufen.
Stattdessen kann die Server Function eine Antwort an den Client geben, die den Browser anweist, auf eine bestimmte Weise zu navigieren.
In Kombination mit einem loader von TanStack Router können wir eine Erfahrung ähnlich einer Single-Page-Anwendung bereitstellen, selbst wenn JavaScript deaktiviert ist; alles, indem wir den Browser anweisen, die aktuelle Seite mit neuen Daten, die durch den loader geleitet werden, neu zu laden
import * as fs from 'fs'
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const filePath = 'count.txt'
async function readCount() {
return parseInt(
await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
)
}
const getCount = createServerFn({
method: 'GET',
}).handler(() => {
return readCount()
})
const updateCount = createServerFn({ method: 'POST' })
.validator((formData) => {
if (!(formData instanceof FormData)) {
throw new Error('Invalid form data')
}
const addBy = formData.get('addBy')
if (!addBy) {
throw new Error('addBy is required')
}
return parseInt(addBy.toString())
})
.handler(async ({ data: addByAmount }) => {
const count = await readCount()
await fs.promises.writeFile(filePath, `${count + addByAmount}`)
// Reload the page to trigger the loader again
return new Response('ok', { status: 301, headers: { Location: '/' } })
})
export const Route = createFileRoute('/')({
component: Home,
loader: async () => await getCount(),
})
function Home() {
const state = Route.useLoaderData()
return (
<div>
<form
action={updateCount.url}
method="POST"
encType="multipart/form-data"
>
<input type="number" name="addBy" defaultValue="1" />
<button type="submit">Add</button>
</form>
<pre>{state}</pre>
</div>
)
}
import * as fs from 'fs'
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const filePath = 'count.txt'
async function readCount() {
return parseInt(
await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
)
}
const getCount = createServerFn({
method: 'GET',
}).handler(() => {
return readCount()
})
const updateCount = createServerFn({ method: 'POST' })
.validator((formData) => {
if (!(formData instanceof FormData)) {
throw new Error('Invalid form data')
}
const addBy = formData.get('addBy')
if (!addBy) {
throw new Error('addBy is required')
}
return parseInt(addBy.toString())
})
.handler(async ({ data: addByAmount }) => {
const count = await readCount()
await fs.promises.writeFile(filePath, `${count + addByAmount}`)
// Reload the page to trigger the loader again
return new Response('ok', { status: 301, headers: { Location: '/' } })
})
export const Route = createFileRoute('/')({
component: Home,
loader: async () => await getCount(),
})
function Home() {
const state = Route.useLoaderData()
return (
<div>
<form
action={updateCount.url}
method="POST"
encType="multipart/form-data"
>
<input type="number" name="addBy" defaultValue="1" />
<button type="submit">Add</button>
</form>
<pre>{state}</pre>
</div>
)
}
Bei der Verwendung von Prerendering/Static Generation können Server Functions auch "statisch" sein, was es ermöglicht, ihre Ergebnisse zur Build-Zeit zu cachen und als statische Assets auszuliefern.
Erfahren Sie alles über dieses Muster auf der Seite Static Server Functions.
Unter der Haube werden Server Functions aus dem Client-Bundle in ein separates Server-Bundle extrahiert. Auf dem Server werden sie unverändert ausgeführt und das Ergebnis an den Client zurückgesendet. Auf dem Client leiten Server Functions die Anfrage an den Server weiter, der die Funktion ausführt und das Ergebnis über fetch an den Client zurücksendet.
Der Prozess sieht wie folgt aus
Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.