---
id: server-routes
track: learn
product: start
locale: zh-CN
order: 8
revision: 2
sourceCheckedOn: "2026-08-03"
versionRange: "Start ^1 / Router ^1"
verifiedAgainst: "@tanstack/react-start@1.168.34 + @tanstack/react-router@1.170.18"
contentModel: 2
prerequisites:
  - "理解 Server Function 的应用内 RPC 边界"
  - "了解基本 HTTP 状态码"
sourceRefs:
  - "https://tanstack.com/start/latest/docs/framework/react/guide/server-routes"
  - "https://tanstack.com/start/latest/docs/framework/react/guide/server-functions"
---

# 把外部入口放进 Server Route

> 区分应用内部 RPC 与公开 HTTP 端点，并用原生 Response 明确表达状态码、正文和缓存策略。

**Outcome:** 能判断何时使用 Server Route，并为成功与失败返回正确的 HTTP 响应。

## 先判断谁会调用

只由当前 Start 应用调用并希望保留端到端类型时，优先 Server Function。Webhook、第三方客户端、移动端或需要稳定 HTTP 契约的入口使用 Server Route。两者都在服务端执行，但面对的是不同调用方。

## 返回完整的 HTTP 语义

Server Route handler 直接返回 Response。资源不存在时显式设置 404，不要抛页面路由使用的 notFound()；成功响应也要根据数据是否与身份相关选择 no-store、private 或可共享缓存。

File: `src/routes/api/issues/$issueId.ts`

```ts
import { createFileRoute } from '@tanstack/react-router'
import { findIssue } from '~/server/issues'

export const Route = createFileRoute('/api/issues/$issueId')({
  server: {
    handlers: {
      GET: async ({ params }) => {
        const issue = await findIssue(params.issueId)
        if (!issue) {
          return Response.json({ error: 'Issue not found' }, { status: 404 })
        }

        return Response.json(issue, {
          headers: { 'Cache-Control': 'no-store' },
        })
      },
    },
  },
})
```

## 把它当成真正的外部边界

路径参数、查询参数、请求头和正文都来自不可信调用方。写入前完成运行时校验、认证、授权与速率限制；错误正文保持稳定，不泄露堆栈、SQL 或内部文件路径。

## 实践与验证

### 完成检查点

外部客户端可用标准 HTTP 请求调用端点，并收到明确的状态码、Header 和响应体。

### 动手练习

增加 `/api/issues/$issueId` GET 端点，并区分成功、参数无效和资源不存在。

### 验证方法

用 curl 验证三种响应，并确认 Content-Type、状态码与 JSON 结构符合契约。

### 常见错误

- 把供外部调用的 API 实现成 Server Function RPC。
- 始终返回 200，再把错误藏进 JSON 字段。

## Official sources

- https://tanstack.com/start/latest/docs/framework/react/guide/server-routes
- https://tanstack.com/start/latest/docs/framework/react/guide/server-functions
