미들웨어
미들웨어란 무엇인가요?
미들웨어를 사용하면 GET/POST 등과 같은 서버 라우트(애플리케이션을 SSR하기 위한 요청 포함)와 createServerFn로 생성된 서버 함수의 동작을 모두 사용자 지정할 수 있습니다. 미들웨어는 조합할 수 있으며, 다른 미들웨어에 의존하여 계층적으로 순서대로 실행되는 작업 체인을 만들 수도 있습니다.
미들웨어로 어떤 작업을 할 수 있나요?
- 인증: 서버 함수를 실행하기 전에 사용자의 신원을 확인합니다.
- 권한 부여: 사용자에게 서버 함수를 실행하는 데 필요한 권한이 있는지 확인합니다.
- 로깅: 요청, 응답 및 오류를 기록합니다.
- CSP: Content Security Policy 및 기타 보안 조치를 구성합니다.
- 관측 가능성: 메트릭, 트레이스 및 로그를 수집합니다.
- 컨텍스트 제공: 다른 미들웨어나 서버 함수에서 사용할 데이터를 요청 객체에 연결합니다.
- 오류 처리: 일관된 방식으로 오류를 처리합니다.
- 이외에도 훨씬 많은 작업을 할 수 있습니다! 가능성은 여러분에게 달려 있습니다!
미들웨어 유형
미들웨어에는 요청 미들웨어와 서버 함수 미들웨어라는 두 가지 유형이 있습니다.
- 요청 미들웨어는 서버 함수를 포함하여 자신을 통과하는 모든 서버 요청의 동작을 사용자 지정하는 데 사용됩니다.
- 서버 함수 미들웨어는 서버 함수의 동작을 구체적으로 사용자 지정하는 데 사용됩니다.
[!NOTE] 서버 함수 미들웨어는 요청 미들웨어의 하위 집합이며, 입력 데이터 검증이나 서버 함수 실행 전후의 클라이언트 측 로직 수행처럼 서버 함수에 특화된 추가 기능을 제공합니다.
주요 차이점
| 기능 | 요청 미들웨어 | 서버 함수 미들웨어 |
|---|---|---|
| 범위 | 모든 서버 요청 | 서버 함수만 |
| 메서드 | .server() | .client(), .server() |
| 입력 검증 | 아니요 | 예 (.validator()) |
| 클라이언트 측 로직 | 아니요 | 예 |
| 의존성 | 요청 미들웨어에 의존할 수 있음 | 두 유형 모두에 의존할 수 있음 |
[!NOTE] 요청 미들웨어는 서버 함수 미들웨어에 의존할 수 없지만, 서버 함수 미들웨어는 요청 미들웨어에 의존할 수 있습니다.
핵심 개념
미들웨어 조합
모든 미들웨어는 조합할 수 있습니다. 즉, 하나의 미들웨어가 다른 미들웨어에 의존할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(() => {
//...
})
const authMiddleware = createMiddleware()
.middleware([loggingMiddleware])
.server(() => {
//...
})
미들웨어 체인 진행하기
미들웨어는 다음 단계로 진행할 수 있으므로, 체인의 다음 미들웨어를 실행하려면 .server 메서드에서(서버 함수 미들웨어를 생성하는 경우에는 .client 메서드에서도) next 함수를 호출해야 합니다. 이를 통해 다음을 수행할 수 있습니다:
- 미들웨어 체인을 중단하고 조기에 반환합니다
- 다음 미들웨어에 데이터를 전달합니다
- 다음 미들웨어의 결과에 접근합니다
- 감싸는 미들웨어에 컨텍스트를 전달합니다
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(async ({ next }) => {
const result = await next() // <-- This will execute the next middleware in the chain
return result
})
요청 미들웨어
요청 미들웨어는 서버 라우트, SSR, 서버 함수를 포함하여 이를 통과하는 모든 서버 요청의 동작을 사용자 지정하는 데 사용됩니다.
요청 미들웨어를 생성하려면 createMiddleware 함수를 호출합니다. type 속성을 'request'로 설정하여 이 함수를 호출할 수 있지만, 이것이 기본값이므로 원하는 경우 생략할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(() => {
//...
})
사용 가능한 메서드
요청 미들웨어에는 다음과 같은 메서드가 있습니다:
middleware: 체인에 미들웨어를 추가합니다.server: 중첩된 미들웨어와 최종적으로 서버 함수보다 먼저 미들웨어가 실행할 서버 측 로직을 정의하고, 그 결과를 다음 미들웨어에도 제공합니다.
.server 메서드
.server 메서드는 중첩된 미들웨어보다 먼저 미들웨어가 실행할 서버 측 로직을 정의하고, 그 결과를 다음 미들웨어에도 제공하는 데 사용됩니다. 이 메서드는 next 메서드와 컨텍스트, 요청 객체 등의 항목을 받습니다:
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(
({ next, context, request }) => {
return next()
},
)
이 핸드셰이크를 빠르게 시각화한 다이어그램은 다음과 같습니다:
sequenceDiagram
HTTP ->> Middleware.server: Request
Middleware.server ->> Middleware.server: next()
Middleware.server ->> ServerFn: payload
ServerFn ->> Middleware.server: result
Middleware.server ->> Middleware.server: return
Middleware.server ->> HTTP: Response
box Server
participant Middleware.server
participant ServerFn
end
서버 라우트에서 요청 미들웨어 사용하기
서버 라우트에서 요청 미들웨어를 사용하는 방법은 두 가지입니다:
모든 서버 라우트 메서드
서버 라우트의 모든 메서드에서 미들웨어를 사용하려면 메서드 빌더 객체의 middleware 속성에 미들웨어 배열을 전달합니다.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(() => {
//...
})
export const Route = createFileRoute('/foo')({
server: {
middleware: [loggingMiddleware],
handlers: {
GET: () => {
//...
},
POST: () => {
//...
},
},
},
})
특정 서버 라우트 메서드
createHandlers 유틸리티를 사용하고 메서드 객체의 middleware 속성에 미들웨어 배열을 전달하여 특정 서버 라우트 메서드에 미들웨어를 전달할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(() => {
//...
})
export const Route = createFileRoute('/foo')({
server: {
handlers: ({ createHandlers }) =>
createHandlers({
GET: {
middleware: [loggingMiddleware],
handler: () => {
//...
},
},
}),
},
})
서버 함수 미들웨어
서버 함수 미들웨어는 요청 미들웨어의 하위 집합으로, 입력 데이터를 검증하거나 서버 함수가 실행되기 전과 후에 모두 클라이언트 측 로직을 수행하는 기능처럼 서버 함수에 특화된 추가 기능을 제공합니다.
서버 함수 미들웨어를 생성하려면 createMiddleware 함수의 type 속성을 'function'으로 설정하여 호출합니다.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' })
.client(() => {
//...
})
.server(() => {
//...
})
사용 가능한 메서드
서버 함수 미들웨어에는 다음 메서드가 있습니다:
middleware: 체인에 미들웨어를 추가합니다.validator: 데이터 객체가 이 미들웨어와 중첩된 미들웨어를 거쳐 최종적으로 서버 함수에 전달되기 전에 수정합니다.client: 서버 함수가 함수를 실행하기 위해 서버를 호출하기 전과 후에 미들웨어가 클라이언트에서 실행할 클라이언트 측 로직을 정의합니다.server: 서버 함수가 실행되기 전과 후에 미들웨어가 서버에서 실행할 서버 측 로직을 정의합니다.
[!NOTE] TypeScript를 사용하고 있다면(사용하기를 권장합니다), 최대한의 추론과 타입 안전성을 보장하도록 타입 시스템이 이러한 메서드의 순서를 강제합니다.
.client 메서드
.client 메서드는 미들웨어가 서버로 보내는 RPC 호출의 실행과 결과를 감싸도록 클라이언트 측 로직을 정의하는 데 사용됩니다.
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next, context }) => {
const result = await next() // <-- This will execute the next middleware in the chain and eventually, the RPC to the server
return result
},
)
.validator 메서드
validator 메서드는 데이터 객체가 이 미들웨어와 중첩된 미들웨어를 거쳐 최종적으로 서버 함수에 전달되기 전에 수정하는 데 사용됩니다. 이 메서드는 데이터 객체를 받아 검증된(선택적으로 수정된) 데이터 객체를 반환하는 함수를 인자로 받아야 합니다. 일반적으로 zod 같은 검증 라이브러리를 사용합니다.
import { createMiddleware } from '@tanstack/react-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const mySchema = z.object({
workspaceId: z.string(),
})
const workspaceMiddleware = createMiddleware({ type: 'function' })
.validator(zodValidator(mySchema))
.server(({ next, data }) => {
console.log('Workspace ID:', data.workspaceId)
return next()
})
서버 함수 미들웨어 사용하기
미들웨어가 특정 서버 함수를 감싸도록 하려면 미들웨어 배열을 createServerFn 함수의 middleware 속성에 전달하면 됩니다.
import { createServerFn } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'
const fn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
//...
})
이 핸드셰이크를 빠르게 파악할 수 있도록 다음 다이어그램을 참고하세요:
sequenceDiagram
ServerFn (client) ->> Middleware.client: payload
Middleware.client ->> Middleware.client: next()
Middleware.client ->> Middleware.server: Request
Middleware.server ->> Middleware.server: next()
Middleware.server ->> ServerFn: payload
ServerFn ->> Middleware.server: result
Middleware.server ->> Middleware.server: return
Middleware.server ->> Middleware.client: Response
Middleware.client ->> Middleware.client: return
Middleware.client ->> ServerFn (client): result
box Client
participant ServerFn (client)
participant Middleware.client
end
box Server
participant Middleware.server
participant ServerFn
end
컨텍스트 관리
next을 통한 컨텍스트 제공
next 함수는 객체 값을 가진 context 속성이 있는 객체를 인자로 선택적으로 호출할 수 있습니다. 이 context 값에 전달하는 모든 속성은 상위 context에 병합되어 다음 미들웨어에 제공됩니다.
import { createMiddleware } from '@tanstack/react-start'
const awesomeMiddleware = createMiddleware({ type: 'function' }).server(
({ next }) => {
return next({
context: {
isAwesome: Math.random() > 0.5,
},
})
},
)
const loggingMiddleware = createMiddleware({ type: 'function' })
.middleware([awesomeMiddleware])
.server(async ({ next, context }) => {
console.log('Is awesome?', context.isAwesome)
return next()
})
클라이언트 컨텍스트를 서버로 전송하기
의도치 않게 큰 페이로드가 서버로 전송될 수 있으므로 기본적으로 클라이언트 컨텍스트는 서버로 전송되지 않습니다. 클라이언트 컨텍스트를 서버로 보내야 한다면 서버로 데이터를 전송할 sendContext 속성과 객체를 사용하여 next 함수를 호출해야 합니다. sendContext에 전달된 모든 속성은 병합 및 직렬화되어 데이터와 함께 서버로 전송되며, 중첩된 모든 서버 미들웨어의 일반 컨텍스트 객체에서 사용할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
// Send the workspace ID to the server
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, data, context }) => {
// Woah! We have the workspace ID from the client!
console.log('Workspace ID:', context.workspaceId)
return next()
})
클라이언트가 전송한 컨텍스트의 보안
위 예시에서 클라이언트가 전송한 컨텍스트는 타입 안전하지만 런타임에 반드시 검증할 필요는 없다는 점을 눈치챘을 수 있습니다. 컨텍스트를 통해 동적인 사용자 생성 데이터를 전달하면 보안 문제가 발생할 수 있으므로, 컨텍스트를 통해 클라이언트에서 서버로 동적 데이터를 전송하는 경우 사용하기 전에 서버 측 미들웨어에서 검증해야 합니다.
형식 검증은 권한 부여가 아닙니다. 파싱된 UUID/숫자는 올바른 형식의 식별자일 뿐, 권한이 부여된 식별자는 아닙니다. 해당 값을 쿼리 키, 필터 또는 경로 매개변수, 즉 읽거나 쓸 행을 선택하는 용도로 사용하려면 세션 주체가 해당 값에 접근할 수 있는지도 확인해야 합니다. 그렇지 않으면 로그인한 사용자가 자신의 요청에서 값을 바꿔 다른 테넌트의 데이터를 차례로 탐색할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
import { z } from 'zod'
const requestLogger = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.middleware([authMiddleware]) // session loaded server-side, NOT from sendContext
.server(async ({ next, context }) => {
// 1. Validate shape
const workspaceId = z.string().uuid().parse(context.workspaceId)
// 2. Validate access — does this session principal have membership?
const member = await db.memberships.find({
userId: context.session.userId,
workspaceId,
})
if (!member) throw new Error('Not a member of this workspace')
// 3. Now safe to use as a query key.
return next({ context: { workspaceId } })
})
세션 자체는 항상 서버가 신뢰하는 소스(authMiddleware의 쿠키 + DB 조회)에서 가져와야 하며, 절대로 sendContext에서 가져오면 안 됩니다. 클라이언트가 보낼 수 있는 정보라면 무엇이든 클라이언트가 거짓으로 보낼 수 있습니다.
서버 컨텍스트를 클라이언트로 보내기
클라이언트 컨텍스트를 서버로 보내는 것과 마찬가지로, sendContext 속성과 클라이언트로 전송할 데이터 객체를 사용해 next 함수를 호출하여 서버 컨텍스트를 클라이언트로 보낼 수도 있습니다. sendContext에 전달된 모든 속성은 병합 및 직렬화되어 응답과 함께 클라이언트로 전송되며, 중첩된 모든 클라이언트 미들웨어의 일반 컨텍스트 객체에서 사용할 수 있습니다. client에서 next를 호출해 반환된 객체에는 서버에서 클라이언트로 보낸 컨텍스트가 포함되며 타입 안전성이 보장됩니다.
[!WARNING]
client에서next의 반환 타입은 현재 미들웨어 체인에 알려진 미들웨어에서만 추론할 수 있습니다. 따라서next의 반환 타입은 미들웨어 체인 끝에 있는 미들웨어에서 가장 정확합니다.
import { createMiddleware } from '@tanstack/react-start'
const serverTimer = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
return next({
sendContext: {
// Send the current time to the client
timeFromServer: new Date(),
},
})
},
)
const requestLogger = createMiddleware({ type: 'function' })
.middleware([serverTimer])
.client(async ({ next }) => {
const result = await next()
// Woah! We have the time from the server!
console.log('Time from the server:', result.context.timeFromServer)
return result
})
전역 미들웨어
전역 미들웨어는 애플리케이션의 모든 요청에 대해 자동으로 실행됩니다. 모든 요청에 적용해야 하는 인증, 로깅, 모니터링과 같은 기능에 유용합니다.
[!NOTE]
src/start.ts파일은 기본 TanStack Start 템플릿에 포함되어 있지 않습니다. 전역 미들웨어나 다른 Start 수준 옵션을 구성하려면 이 파일을 생성해야 합니다.
전역 요청 미들웨어
Start가 처리하는 모든 요청에 미들웨어를 실행하려면 src/start.ts 파일을 생성하고 createStart 함수를 사용하여 미들웨어 구성을 반환합니다:
// src/start.ts
import { createStart, createMiddleware } from '@tanstack/react-start'
const myGlobalMiddleware = createMiddleware().server(() => {
//...
})
export const startInstance = createStart(() => {
return {
requestMiddleware: [myGlobalMiddleware],
}
})
[!NOTE] 전역 요청 미들웨어는 서버 라우트, SSR, 서버 함수를 포함한 모든 요청보다 먼저 실행됩니다.
CSRF 미들웨어
서버 함수는 동일 출처 RPC 엔드포인트이므로 교차 사이트 요청으로부터 보호해야 합니다. 앱에서 src/start.ts를 정의하지 않으면 TanStack Start가 서버 함수용 CSRF 미들웨어를 자동으로 설치합니다.
사용자 지정 src/start.ts을 정의하는 경우 createCsrfMiddleware()을 명시적으로 추가합니다:
// src/start.ts
import { createStart, createCsrfMiddleware } from '@tanstack/react-start'
const csrfMiddleware = createCsrfMiddleware({
filter: (ctx) => ctx.handlerType === 'serverFn',
})
export const startInstance = createStart(() => ({
requestMiddleware: [csrfMiddleware],
}))
기본적으로 Origin 및 Referer 검사는 들어오는 요청 URL의 출처와 비교합니다. 배포 환경에서 다른 공개 출처를 허용해야 한다면 createCsrfMiddleware({ origin: 'https://app.example.com' })을 사용하여 CSRF 미들웨어에 구성합니다.
기본적으로 createCsrfMiddleware()은 미들웨어가 처리하는 모든 요청을 검증합니다. 서버 함수 보호를 위해 전역으로 설치할 때는 filter: (ctx) => ctx.handlerType === 'serverFn'을 사용합니다. 이 미들웨어는 Sec-Fetch-Site, Origin 또는 Referer 헤더를 사용하여 동일 출처 브라우저 요청 메타데이터를 확인하고, 동일 출처임을 입증할 수 없는 요청을 거부합니다.
동일한 미들웨어를 사용해 다른 모든 라우트도 보호할 수 있습니다.
export const Route = createFileRoute('/api/foo')({
server: {
middleware: [createCsrfMiddleware()],
handlers: { GET: () => {...} }
}
})
CSRF 미들웨어 없이 src/start.ts을 정의하면 Start는 서버 함수 요청에 대해 개발 경고를 표시합니다. 의도적으로 다른 방식으로 CSRF를 처리하는 경우 다음과 같이 경고를 비활성화합니다.
// vite.config.ts or rsbuild.config.ts
tanstackStart({
serverFns: {
disableCsrfMiddlewareWarning: true,
},
})
전역 서버 함수 미들웨어
미들웨어가 애플리케이션의 모든 서버 함수에서 실행되도록 하려면 src/start.ts 파일의 functionMiddleware 배열에 추가합니다.
// src/start.ts
import { createStart } from '@tanstack/react-start'
import { loggingMiddleware } from './middleware'
export const startInstance = createStart(() => {
return {
functionMiddleware: [loggingMiddleware],
}
})
미들웨어 실행 순서
미들웨어는 전역 미들웨어부터 시작해 서버 함수 미들웨어가 이어지는 종속성 우선 순서로 실행됩니다. 다음 예제에서는 아래 순서로 로그가 기록됩니다.
globalMiddleware1globalMiddleware2abcdfn
import { createMiddleware, createServerFn } from '@tanstack/react-start'
const globalMiddleware1 = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('globalMiddleware1')
return next()
},
)
const globalMiddleware2 = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
console.log('globalMiddleware2')
return next()
},
)
const a = createMiddleware({ type: 'function' }).server(async ({ next }) => {
console.log('a')
return next()
})
const b = createMiddleware({ type: 'function' })
.middleware([a])
.server(async ({ next }) => {
console.log('b')
return next()
})
const c = createMiddleware({ type: 'function' })
.middleware()
.server(async ({ next }) => {
console.log('c')
return next()
})
const d = createMiddleware({ type: 'function' })
.middleware([b, c])
.server(async () => {
console.log('d')
})
const fn = createServerFn()
.middleware([d])
.server(async () => {
console.log('fn')
})
요청 및 응답 수정
서버 응답 읽기/수정하기
server 메서드를 사용하는 미들웨어는 서버 함수와 동일한 컨텍스트에서 실행되므로, 동일한 서버 함수 컨텍스트 유틸리티를 그대로 사용해 요청 헤더, 상태 코드 등을 읽고 수정할 수 있습니다.
클라이언트 요청 수정하기
client 메서드를 사용하는 미들웨어는 서버 함수와 완전히 다른 클라이언트 측 컨텍스트에서 실행되므로, 동일한 유틸리티를 사용해 요청을 읽고 수정할 수 없습니다. 하지만 next 함수를 호출할 때 추가 속성을 반환하여 요청을 수정할 수 있습니다.
사용자 지정 헤더 설정하기
next에 headers 객체를 전달하여 나가는 요청에 헤더를 추가할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
import { getToken } from 'my-auth-library'
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
Authorization: `Bearer ${getToken()}`,
},
})
},
)
미들웨어 간 헤더 병합
여러 미들웨어가 헤더를 설정하면 헤더가 함께 병합됩니다. 나중에 실행되는 미들웨어는 새 헤더를 추가하거나 이전 미들웨어가 설정한 헤더를 재정의할 수 있습니다.
import { createMiddleware } from '@tanstack/react-start'
const firstMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
'X-Request-ID': '12345',
'X-Source': 'first-middleware',
},
})
},
)
const secondMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: {
'X-Timestamp': Date.now().toString(),
'X-Source': 'second-middleware', // Overrides first middleware
},
})
},
)
// Final headers will include:
// - X-Request-ID: '12345' (from first)
// - X-Timestamp: '<timestamp>' (from second)
// - X-Source: 'second-middleware' (second overrides first)
호출 위치에서 직접 헤더를 설정할 수도 있습니다.
await myServerFn({
data: { name: 'John' },
headers: {
'X-Custom-Header': 'call-site-value',
},
})
헤더 우선순위(모든 헤더가 병합되며 나중 값이 이전 값을 재정의함):
- 이전 미들웨어의 헤더
- 이후 미들웨어의 헤더(이전 헤더 재정의)
- 호출 위치의 헤더(모든 미들웨어 헤더 재정의)
사용자 지정 Fetch 구현
고급 사용 사례에서는 서버 함수 요청이 이루어지는 방식을 제어하기 위해 사용자 지정 fetch 구현을 제공할 수 있습니다. 이는 다음과 같은 경우에 유용합니다.
- 요청 인터셉터 또는 재시도 로직 추가
- 사용자 지정 HTTP 클라이언트 사용
- 테스트 및 모킹
- 텔레메트리 또는 모니터링 추가
클라이언트 미들웨어를 통한 방식:
import { createMiddleware } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'
const customFetchMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const customFetch: CustomFetch = async (url, init) => {
console.log('Request starting:', url)
const start = Date.now()
const response = await fetch(url, init)
console.log('Request completed in', Date.now() - start, 'ms')
return response
}
return next({ fetch: customFetch })
},
)
호출 지점에서 직접 지정하는 방식:
import type { CustomFetch } from '@tanstack/react-start'
const myFetch: CustomFetch = async (url, init) => {
// Add custom logic here
return fetch(url, init)
}
await myServerFn({
data: { name: 'John' },
fetch: myFetch,
})
Fetch 재정의 우선순위
여러 수준에서 사용자 지정 fetch 구현을 제공하면 다음 우선순위가 적용됩니다(우선순위가 높은 순서에서 낮은 순서):
| 우선순위 | 출처 | 설명 |
|---|---|---|
| 1 (가장 높음) | 호출 지점 | serverFn({ fetch: customFetch }) |
| 2 | 뒤쪽 미들웨어 | 체인에서 fetch을 제공하는 마지막 미들웨어 |
| 3 | 앞쪽 미들웨어 | 체인에서 fetch을 제공하는 첫 번째 미들웨어 |
| 4 | createStart | createStart({ serverFns: { fetch: customFetch } }) |
| 5 (가장 낮음) | 기본값 | 전역 fetch 함수 |
핵심 원칙: 호출 지점이 항상 우선합니다. 따라서 필요할 때 특정 호출에 대해 미들웨어 동작을 재정의할 수 있습니다.
import { createMiddleware, createServerFn } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'
// Middleware sets a fetch that adds logging
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const loggingFetch: CustomFetch = async (url, init) => {
console.log('Middleware fetch:', url)
return fetch(url, init)
}
return next({ fetch: loggingFetch })
},
)
const myServerFn = createServerFn()
.middleware([loggingMiddleware])
.handler(async () => {
return { message: 'Hello' }
})
// Uses middleware's loggingFetch
await myServerFn()
// Override with custom fetch for this specific call
const testFetch: CustomFetch = async (url, init) => {
console.log('Test fetch:', url)
return fetch(url, init)
}
await myServerFn({ fetch: testFetch }) // Uses testFetch, NOT loggingFetch
체이닝된 미들웨어 예시:
여러 미들웨어가 fetch를 제공하면 마지막 미들웨어가 우선합니다:
import { createMiddleware, createServerFn } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'
const firstMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const firstFetch: CustomFetch = (url, init) => {
const headers = new Headers(init?.headers)
headers.set('X-From', 'first-middleware')
return fetch(url, { ...init, headers })
}
return next({ fetch: firstFetch })
},
)
const secondMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const secondFetch: CustomFetch = (url, init) => {
const headers = new Headers(init?.headers)
headers.set('X-From', 'second-middleware')
return fetch(url, { ...init, headers })
}
return next({ fetch: secondFetch })
},
)
const myServerFn = createServerFn()
.middleware([firstMiddleware, secondMiddleware])
.handler(async () => {
// Request will have X-From: 'second-middleware'
// because secondMiddleware's fetch overrides firstMiddleware's fetch
return { message: 'Hello' }
})
createStart를 통한 전역 Fetch:
createStart에 serverFns.fetch을 제공하여 애플리케이션의 모든 서버 함수에 기본 사용자 지정 fetch를 설정할 수 있습니다. 이는 전역 요청 인터셉터, 재시도 로직 또는 텔레메트리를 추가할 때 유용합니다:
// src/start.ts
import { createStart } from '@tanstack/react-start'
import type { CustomFetch } from '@tanstack/react-start'
const globalFetch: CustomFetch = async (url, init) => {
console.log('Global fetch:', url)
// Add retry logic, telemetry, etc.
return fetch(url, init)
}
export const startInstance = createStart(() => {
return {
serverFns: {
fetch: globalFetch,
},
}
})
이 전역 fetch는 미들웨어 및 호출 지점의 fetch보다 우선순위가 낮으므로, 필요할 때 특정 서버 함수 또는 호출에 대해 계속 재정의할 수 있습니다.
[!NOTE] 사용자 지정 fetch는 클라이언트 측에만 적용됩니다. SSR 중에는 fetch를 거치지 않고 서버 함수를 직접 호출합니다.
환경 및 성능
환경 트리 셰이킹
생성되는 각 번들의 환경에 따라 미들웨어 기능이 트리 셰이킹됩니다.
- 서버에서는 아무것도 트리 셰이킹되지 않으므로 미들웨어에서 사용하는 모든 코드가 서버 번들에 포함됩니다.
- 클라이언트에서는 모든 서버 전용 코드가 클라이언트 번들에서 제거됩니다. 즉,
server메서드에서 사용하는 모든 코드는 항상 클라이언트 번들에서 제거됩니다.data검증 코드도 제거됩니다.
미들웨어 팩토리
정적 미들웨어는 한 번 생성되어 여러 라우트에서 재사용됩니다. 미들웨어 팩토리는 이러한 생성을 함수로 감싸 매개변수를 받을 수 있게 하며, 호출자의 요구 사항에 따라 다르게 동작하도록 합니다. 권한 부여가 일반적인 사용 사례입니다.
인증(정적 기본 미들웨어) 예시:
이 미들웨어는 세션을 검증하고 다운스트림 미들웨어에서 사용할 수 있도록 세션을 context에 주입합니다.
인증이 필요한 모든
createServerFn에authMiddleware를 연결합니다. 서버 함수는 API 엔드포인트이므로 비공개 데이터를 읽거나 변경하는 엔드포인트를 보호합니다. 라우트beforeLoad가드는 라우트 UX를 개선하지만 데이터 경계는 아닙니다. 인증 서버 프리미티브를 참조하세요.
// middleware.ts
import { createMiddleware } from '@tanstack/react-start'
import { auth } from './my-auth'
export const authMiddleware = createMiddleware().server(
async ({ next, request }) => {
const session = await auth.getSession({ headers: request.headers })
if (!session) {
throw new Error('Unauthorized')
}
return await next({
context: { session },
})
},
)
권한 부여(미들웨어 팩토리) 예시:
이 미들웨어는 동적 permissions 매개변수를 기반으로 접근 권한을 검증하며, authMiddleware와 합성되므로 context.session를 이미 사용할 수 있습니다.
// middleware.ts
import { createMiddleware } from '@tanstack/react-start'
import { auth } from './my-auth'
export const authMiddleware = createMiddleware().server(
async ({ next, request }) => {
// ... (implementation from authentication example above)
},
)
type Permissions = Record<string, string[]>
export function authorizationMiddleware(permissions: Permissions) {
return createMiddleware({ type: 'function' })
.middleware([authMiddleware])
.server(async ({ next, context }) => {
const granted = await auth.hasPermission(context.session, permissions)
if (!granted) {
throw new Error('Forbidden')
}
return await next()
})
}
서버 함수에서 사용:
미들웨어 로직을 중복하지 않고 서버 함수별로 접근 요구 사항을 정의합니다.
import { createServerFn } from '@tanstack/react-start'
import { authorizationMiddleware } from './middleware'
export const getClients = createServerFn()
.middleware([
authorizationMiddleware({
client: ['read'],
}),
])
.handler(async ({ context }) => {
return { message: 'The user can read clients.' }
})