Server Functions
What are Server Functions?
서버 함수는 어디에서나(심지어 클라이언트에서도) 호출할 수 있지만, 서버에서만 실행되는 로직을 지정할 수 있도록 합니다. 사실, 서버 함수는 API 경로와 크게 다르지 않지만, 몇 가지 주요 차이점이 있습니다:
- HTTP를 통해 공개적으로 접근할 수 없습니다.
- 로더, 훅, 컴포넌트 등을 포함하여 애플리케이션 어디에서든 호출할 수 있습니다.
하지만 다음과 같은 점에서 일반적인 API 경로와 유사합니다:
- 요청 컨텍스트에 접근할 수 있어 헤더를 읽거나, 쿠키를 설정하거나, 기타 작업을 수행할 수 있습니다.
- 환경 변수와 같은 민감한 정보에 접근할 수 있지만 이를 클라이언트에 노출하지 않습니다.
- 데이터베이스에서 데이터를 가져오거나, 이메일을 보내거나, 기타 서비스를 호출하는 등 서버 측 로직을 수행할 수 있습니다.
- 원시 값, JSON 직렬화 가능한 객체, 심지어 Raw Response 객체까지 반환할 수 있습니다.
- 리디렉션이나 notFound와 같은 에러를 발생시킬 수 있으며, 이는 라우터에 의해 자동으로 처리될 수 있습니다.
서버 함수가 "React Server Functions"와 다른 점은 무엇인가요?
- TanStack Server Functions는 특정 프론트엔드 프레임워크에 종속되지 않으며, 어떤 프론트엔드 프레임워크와도, 또는 아예 없이도 사용할 수 있습니다.
- TanStack Server Functions는 표준 HTTP 요청을 기반으로 하며, 병렬 실행 병목현상 없이 원하는 만큼 호출할 수 있습니다.
How do they work?
서버 함수는 애플리케이션 어디에서든 정의할 수 있지만, 파일의 최상위 레벨에서 정의되어야 합니다. 로더, 훅 등 애플리케이션 어디에서든 호출할 수 있습니다. 전통적으로 이 패턴은 원격 프로시저 호출(RPC)로 알려져 있지만, 이러한 함수의 동형적 특성 때문에 이를 서버 함수라고 합니다.
- 서버 번들에서는 서버 함수 로직이 그대로 유지됩니다. 이미 올바른 위치에 있으므로 추가 작업이 필요하지 않습니다.
- 클라이언트에서는 서버 전용 로직이 클라이언트 번들에서 제거되고, 호출 시 서버에 fetch 요청을 보내 서버 번들에서 서버 함수를 실행한 후 응답을 클라이언트로 전송하는 함수로 대체됩니다.
Server Function Middleware
서버 함수는 미들웨어를 사용하여 로직, 컨텍스트, 공통 작업, 전제 조건 등을 공유할 수 있습니다. 서버 함수 미들웨어에 대해 더 알고 싶다면 Middleware guide를 참조하세요.
Defining Server Functions
TanStack Start의 서버 함수 설계에 영감을 주고 구현을 지도해준 tRPC (opens in a new tab) 팀에게 감사를 전합니다. 우리는 API 경로에 tRPC를 사용하는 것을 매우 추천하며, TanStack Server Functions 또한 동일한 1급 지원과 개발자 경험을 제공하도록 설계했습니다. 감사합니다!
서버 함수는 @tanstack/start
패키지에서 내보내는 createServerFn
함수를 사용하여 정의됩니다. 이 함수는 HTTP 메서드와 서버에서 실행될 비동기 함수를 매개변수로 받아야 합니다. 아래는 예제입니다:
// getServerTime.ts
import { createServerFn } from "@tanstack/start";
export const getServerTime = createServerFn().handler(async () => {
// 1초 동안 대기
await new Promise((resolve) => setTimeout(resolve, 1000));
// 현재 시간을 반환
return new Date().toISOString();
});
Where can I call server functions?
- 서버 측 코드에서
- 클라이언트 측 코드에서
- 다른 서버 함수에서
- 어디서든 가능합니다!
Accepting Parameters
서버 함수는 다양한 유형의 단일 매개변수를 받을 수 있습니다:
- 기본 자료형
string
number
boolean
null
Array
Object
- FormData
- ReadableStream (위의 모든 유형)
- Promise (위의 모든 유형)
아래는 단순한 문자열 매개변수를 받는 서버 함수의 예제입니다:
import { createServerFn } from "@tanstack/start";
export const greet = createServerFn({
method: "GET",
})
.validator((data: string) => data)
.handler(async (ctx) => {
return `Hello, ${ctx.data}!`;
});
greet({
data: "John",
});
Validation / Runtime Type Safety
서버 함수는 입력 데이터를 런타임에서 검증하도록 구성할 수 있습니다. 이는 입력이 실행 전에 올바른 유형인지 확인하고, 친절한 에러 메시지를 제공하며, 올바르게 구성된 경우 타입 안전성을 강화하는 데 유용합니다.
검증을 활성화하려면 서버 함수의 validator
메서드를 호출하십시오. 다음을 매개변수로 전달할 수 있습니다:
- 유효한 값과 유형을 반환하는 함수
- 입력이 유효하지 않음을 나타내기 위한 에러
- 입력 데이터의 형태를 정의하는 스키마 객체(선택한 검증 라이브러리에 따라 스키마 객체의 정확한 형태가 달라질 수 있음)
Basic Validation
다음은 입력 매개변수를 검증하는 간단한 서버 함수 예제입니다:
import { createServerFn } from "@tanstack/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}!`;
});
Using a Validation Library
Zod와 같은 검증 라이브러리를 사용하여 입력 매개변수를 검증할 수도 있습니다:
import { createServerFn } from "@tanstack/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",
},
});
Type Safety
서버 함수는 네트워크 경계를 넘기 때문에, 전달되는 데이터가 올바른 유형일 뿐만 아니라 런타임에서도 검증되는 것이 중요합니다. 이는 특히 사용자 입력을 처리할 때 중요하며, 예상치 못한 입력이 들어올 수 있기 때문입니다. 개발자가 입출력 데이터를 검증하도록 돕기 위해, 타입은 input
검증을 사용하는 데 의존하며, 이를 통해 서버 함수의 입력 및 출력 타입을 유추할 수 있습니다.
import { createServerFn } from "@tanstack/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'.
}
Inference
서버 함수는 validator
핸들러와 handler
함수의 반환 타입을 기반으로 입력 및 출력 타입을 유추합니다. 사실, 전달된 validator
는 자체적으로 별도의 입력/출력 타입을 가질 수 있으며, 이는 입력 데이터를 변환하는 경우 유용할 수 있습니다.
아래는 zod
검증 라이브러리를 사용하는 예제입니다:
import { createServerFn } from "@tanstack/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
},
});
Non-Validated Inference
네트워크 I/O 데이터를 검증하는 데 검증 라이브러리를 사용하는 것을 강력히 추천하지만, 어떤 이유로든 데이터를 검증하지 않고도 타입 안전성을 유지하려는 경우가 있을 수 있습니다. 이를 위해, 입력 또는 출력을 올바른 타입으로 캐스팅하는 validator
핸들러로서의 아이덴티티 함수를 제공하여 서버 함수에 타입 정보를 전달할 수 있습니다:
import { createServerFn } from "@tanstack/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",
},
});
JSON Parameters
서버 함수는 JSON 직렬화 가능한 객체를 매개변수로 받을 수 있습니다. 이는 복잡한 데이터 구조를 서버로 전달할 때 유용합니다:
import { createServerFn } from "@tanstack/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,
},
});
FormData Parameters
서버 함수는 FormData
객체를 매개변수로 받을 수 있습니다:
import { createServerFn } from "@tanstack/start";
export const greetUser = createServerFn()
.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>
);
}
Server Function Context
서버 함수가 받는 단일 매개변수 외에도, vinxi/http
의 다양한 유틸리티를 사용하여 서버 요청 컨텍스트에 접근할 수 있습니다. Vinxi는 내부적으로 unjs
의 h3
패키지를 사용하여 플랫폼 간 HTTP 요청을 수행합니다.
다양한 컨텍스트 함수는 다음 작업에 사용할 수 있습니다:
- 요청 컨텍스트 접근
- 헤더 접근/설정
- 세션/쿠키 접근/설정
- 응답 상태 코드 및 상태 메시지 설정
- 다중 파트 폼 데이터 처리
- 사용자 지정 서버 컨텍스트 속성 읽기/설정
사용 가능한 컨텍스트 함수의 전체 목록은 h3 Methods (opens in a new tab) 또는 Vinxi Exports Source Code (opens in a new tab)를 참조하세요.
먼저 몇 가지 예제를 살펴보겠습니다:
Accessing the Request Context
Vinxi의 getWebRequest
함수를 사용하여 서버 함수 내부에서 요청 자체에 접근할 수 있습니다:
import { createServerFn } from "@tanstack/start";
import { getWebRequest } from "vinxi/http";
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
}
);
Accessing Headers
Vinxi의 getHeaders
함수를 사용하여 서버 함수 내부에서 모든 헤더에 접근할 수 있습니다:
import { createServerFn } from "@tanstack/start";
import { getHeaders } from "vinxi/http";
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",
// ...
// }
}
);
개별 헤더에 접근하려면 getHeader
함수를 사용할 수도 있습니다:
import { createServerFn } from "@tanstack/start";
import { getHeader } from "vinxi/http";
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
}
);
Returning Values
서버 함수는 여러 유형의 값을 반환할 수 있습니다:
- 기본 자료형
- JSON 직렬화 가능한 객체
redirect
에러 (또는 throw 가능)notFound
에러 (또는 throw 가능)- Raw Response 객체
Returning Primitives and JSON
기본 자료형 또는 JSON 직렬화 가능한 객체를 반환하려면 단순히 값을 서버 함수에서 반환하면 됩니다:
import { createServerFn } from "@tanstack/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!",
};
}
);
기본적으로 서버 함수는 반환된 모든 비-Response 객체를 기본 자료형 또는 JSON 직렬화 가능한 객체로 간주합니다.
Responding with Custom Headers
사용자 지정 헤더를 응답으로 보내려면 Vinxi의 setHeader
함수를 사용할 수 있습니다:
import { createServerFn } from "@tanstack/start";
import { setHeader } from "vinxi/http";
export const getServerTime = createServerFn({ method: "GET" }).handler(
async () => {
setHeader("X-Custom-Header", "value");
return new Date().toISOString();
}
);
Responding with Custom Status Codes
사용자 지정 상태 코드를 응답으로 보내려면 Vinxi의 setResponseStatus
함수를 사용할 수 있습니다:
import { createServerFn } from "@tanstack/start";
import { setResponseStatus } from "vinxi/http";
export const getServerTime = createServerFn({ method: "GET" }).handler(
async () => {
setResponseStatus(201);
return new Date().toISOString();
}
);
Returning Raw Response objects
Raw Response 객체를 반환하려면 단순히 Response 객체를 서버 함수에서 반환하면 됩니다:
import { createServerFn } from "@tanstack/start";
export const getServerTime = createServerFn({ method: "GET" }).handler(
async () => {
// s3에서 파일 읽기
return fetch("https://example.com/time.txt");
}
);
Throwing Errors
특별한 redirect
및 notFound
에러 외에도 서버 함수는 사용자 정의 에러를 throw할 수 있습니다. 이러한 에러는 JSON 응답으로 직렬화되어 클라이언트로 전송되며 상태 코드 500이 포함됩니다.
import { createServerFn } from "@tanstack/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)"
// }
}
}
Calling server functions from within route lifecycles
서버 함수는 라우트의 loader
, beforeLoad
, 또는 기타 라우터 제어 API에서 일반적으로 호출할 수 있습니다. 이러한 API는 서버 함수에서 throw된 에러, 리디렉션, 또는 notFound를 자동으로 처리할 수 있습니다.
import { getServerTime } from "./getServerTime";
export const Route = createFileRoute("/time")({
loader: async () => {
const time = await getServerTime();
return {
time,
};
},
});
Calling server functions from hooks and components
서버 함수는 redirect
또는 notFound
를 throw할 수 있으며, 반드시 필요한 것은 아니지만 이를 적절히 처리하는 것을 권장합니다. 이를 더 쉽게 하기 위해 @tanstack/start
패키지는 useServerFn
훅을 내보내며, 이를 사용하여 서버 함수를 컴포넌트와 훅에 바인딩할 수 있습니다:
import { useServerFn } from "@tanstack/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(),
});
}
Calling server functions anywhere else
서버 함수는 단순히 비동기 함수이므로 애플리케이션 어디에서든 호출할 수 있습니다. 그러나 서버 함수에서 throw된 리디렉션 또는 notFound는 라우트 라이프사이클 또는 useServerFn
훅을 사용하는 컴포넌트에서 호출되지 않는 한 자동으로 처리되지 않습니다.
Redirects
서버 함수는 redirect
에러를 throw하여 사용자를 다른 URL로 리디렉션할 수 있습니다. 이는 인증, 권한 부여, 또는 사용자를 다른 페이지로 리디렉션해야 하는 기타 시나리오에서 유용합니다.
- SSR 중 리디렉션은 새 위치와 함께 클라이언트로 302 응답을 보냅니다.
- 클라이언트에서 리디렉션은 라우터가 라우트 라이프사이클이나
useServerFn
훅을 사용하는 컴포넌트 내에서 자동으로 처리합니다. 서버 함수를 다른 곳에서 호출하면 리디렉션이 자동으로 처리되지 않습니다.
리디렉션을 throw하려면 @tanstack/react-router
패키지에서 내보내는 redirect
함수를 사용할 수 있습니다:
import { redirect } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/start";
export const doStuff = createServerFn({ method: "GET" }).handler(async () => {
// 사용자를 홈 페이지로 리디렉션
throw redirect({
to: "/",
});
});
리디렉션은 router.navigate
, useNavigate()
및 <Link>
컴포넌트와 동일한 옵션을 모두 사용할 수 있습니다. 따라서 다음도 전달할 수 있습니다:
- 경로 매개변수
- 검색 매개변수
- 해시
리디렉션은 또한 status
옵션을 전달하여 응답 상태 코드를 설정할 수 있습니다:
import { redirect } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/start";
export const doStuff = createServerFn({ method: "GET" }).handler(async () => {
// 상태 코드 301과 함께 사용자를 홈 페이지로 리디렉션
throw redirect({
to: "/",
status: 301,
});
});
⚠️ 서버 함수 내에서 Vinxi의
sendRedirect
함수를 사용하여 소프트 리디렉션을 보내지 마세요. 이는Location
헤더를 사용하여 리디렉션을 전송하며, 클라이언트에서 전체 페이지 하드 네비게이션을 강제합니다.
Redirect Headers
리디렉션에 사용자 지정 헤더를 설정하려면 headers
옵션을 전달할 수도 있습니다:
import { redirect } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/start";
export const doStuff = createServerFn({ method: "GET" }).handler(async () => {
// 사용자 지정 헤더와 함께 홈 페이지로 리디렉션
throw redirect({
to: "/",
headers: {
"X-Custom-Header": "value",
},
});
});
Not Found
라우트의 loader
또는 beforeLoad
라이프사이클에서 서버 함수를 호출할 때, 특별한 notFound
에러를 throw하여 요청된 리소스를 찾을 수 없음을 라우터에 알릴 수 있습니다. 이는 단순한 404 상태 코드보다 유용하며, 사용자 정의 404 페이지를 렌더링하거나 에러를 사용자 정의 방식으로 처리할 수 있도록 해줍니다. 서버 함수가 라우트 라이프사이클 외부에서 사용될 때 notFound
가 throw되면 자동으로 처리되지 않습니다.
@tanstack/start
패키지에서 내보내는 notFound
함수를 사용하여 notFound
를 throw할 수 있습니다:
import { createServerFn, notFound } from "@tanstack/start";
const getStuff = createServerFn({ method: "GET" }).handler(async () => {
// 무작위로 not found 에러 반환
if (Math.random() < 0.5) {
throw notFound();
}
// 아니면 데이터를 반환
return {
stuff: "stuff",
};
});
export const Route = createFileRoute("/stuff")({
loader: async () => {
const stuff = await getStuff();
return {
stuff,
};
},
});
Not found 에러는 TanStack Router의 핵심 기능입니다.
Handling Errors
서버 함수가 (리디렉션/notFound
가 아닌) 에러를 throw하면 JSON 응답으로 직렬화되어 상태 코드 500과 함께 클라이언트로 전송됩니다. 이는 디버깅에 유용하지만, 이러한 에러를 더 친화적인 방식으로 처리하고 싶을 수 있습니다. 이는 에러를 잡아 라우트 라이프사이클, 컴포넌트 또는 훅에서 처리하는 방식으로 가능합니다.
import { createServerFn } from "@tanstack/start";
export const doStuff = createServerFn({ method: "GET" }).handler(async () => {
undefined.foo();
});
export const Route = createFileRoute("/stuff")({
loader: async () => {
try {
await doStuff();
} catch (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)"
}
},
});
No-JS Server Functions
JavaScript가 비활성화된 경우, 서버 함수를 실행하는 유일한 방법은 폼을 제출하는 것입니다.
이를 위해 HTML 속성 action
이 포함된 form
요소를 페이지에 추가하면 됩니다.
HTML 속성
action
에 대해 주의하세요. 이 속성은 HTML에서 문자열만 허용하며, 다른 모든 속성과 마찬가지입니다.React 19에서는
action
에 함수를 전달하는 기능 (opens in a new tab)이 추가되었지만, 이는 React 고유의 기능이며 HTML 표준의 일부가 아닙니다.
action
속성은 폼이 제출될 때 폼 데이터를 서버 함수에 보낼 위치를 브라우저에 알려줍니다.
이를 위해 서버 함수의 url
속성을 활용할 수 있습니다:
const yourFn = createServerFn()
.validator((formData) => {
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);
이를 form
의 action
속성에 전달합니다:
function Component() {
return (
<form action={yourFn.url} method="POST">
<input name="name" defaultValue="John" />
<button type="submit">Click me!</button>
</form>
);
}
폼이 제출되면 서버 함수가 실행됩니다.
No-JS Server Function Arguments
폼을 제출할 때 서버 함수에 매개변수를 전달하려면 input
요소와 name
속성을 사용하여 서버 함수에 전달되는 FormData
(opens in a new tab)에 매개변수를 추가할 수 있습니다:
const yourFn = createServerFn()
.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`는 '123'이 됩니다
const age = formData.get("age");
// ...
});
function Component() {
return (
// 데이터를 `multipart/form-data` 형식으로 전송하려면 `encType` 속성을 설정해야 합니다.
<form action={yourFn.url} method="POST" encType="multipart/form-data">
<input name="age" defaultValue="34" />
<button type="submit">Click me!</button>
</form>
);
}
폼이 제출되면 서버 함수가 폼의 데이터를 매개변수로 실행합니다.
No-JS Server Function Return Value
JavaScript가 활성화되어 있든 아니든, 서버 함수는 클라이언트에서 HTTP 요청에 대한 응답을 반환합니다.
JavaScript가 활성화된 경우, 이 응답은 클라이언트의 JavaScript 코드에서 서버 함수의 반환 값으로 접근할 수 있습니다.
const yourFn = createServerFn().handler(async () => {
return "Hello, world!";
});
// `.then`은 JavaScript가 비활성화된 경우 사용할 수 없습니다.
yourFn().then(console.log);
그러나 JavaScript가 비활성화된 경우, 클라이언트의 JavaScript 코드에서 서버 함수의 반환 값에 접근할 방법이 없습니다.
대신, 서버 함수는 클라이언트에 응답을 제공하여 브라우저가 특정 방식으로 탐색하도록 지시할 수 있습니다.
TanStack Router의 loader
와 결합하면, JavaScript가 비활성화된 경우에도 단일 페이지 애플리케이션과 유사한 경험을 제공할 수 있습니다. loader
를 통해 새로운 데이터를 브라우저에 전달하여 현재 페이지를 새로 고치는 방식으로 가능합니다:
import * as fs from "fs";
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/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}`);
// 페이지를 다시 로드하여 loader를 다시 실행
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>
);
}
How are server functions compiled?
서버 함수는 클라이언트 번들에서 추출되어 별도의 서버 번들로 이동됩니다. 서버에서는 서버 함수가 있는 그대로 실행되며, 결과는 클라이언트로 전송됩니다. 클라이언트에서는 서버 함수가 서버로 요청을 프록시하여 서버에서 함수를 실행하고 결과를 클라이언트로 다시 보내는 방식으로 작동합니다.
프로세스는 다음과 같습니다:
createServerFn
이 파일에서 발견되면, 내부 함수가use server
지시문을 사용하는지 확인합니다.use server
지시문이 누락된 경우, 함수 상단에 추가됩니다.- 클라이언트에서는 내부 함수가 클라이언트 번들에서 추출되어 별도의 서버 번들로 이동됩니다.
- 클라이언트 측 서버 함수는 추출된 함수를 실행하기 위해 서버로 요청을 보내는 프록시 함수로 대체됩니다.
- 서버에서는 서버 함수가 추출되지 않고 있는 그대로 실행됩니다.
- 추출이 완료된 후, 각 번들은 데드 코드 제거 프로세스를 적용하여 사용되지 않는 코드를 제거합니다.