or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-router.mdcache-links.mdindex.mdpages-router.mdserver-actions.mdssr.md

app-router.mddocs/

0

# App Router Support

1

2

Experimental features for Next.js App Router including server components, server actions, and advanced caching.

3

4

## Capabilities

5

6

### App Router Client

7

8

Client-side utilities for Next.js App Router with built-in caching and query management.

9

10

```typescript { .api }

11

/**

12

* Creates tRPC client specifically for Next.js App Router with caching support

13

* @param opts - Configuration options for App Router client

14

* @returns tRPC client instance with App Router optimizations

15

*/

16

function experimental_createTRPCNextAppDirClient<TRouter extends AnyRouter>(

17

opts: CreateTRPCNextAppRouterOptions<TRouter>

18

): TRPCClient<TRouter>;

19

20

interface CreateTRPCNextAppRouterOptions<TRouter extends AnyRouter> {

21

/** Function that returns tRPC client configuration */

22

config: () => CreateTRPCClientOptions<TRouter>;

23

}

24

```

25

26

**Usage Examples:**

27

28

```typescript

29

import { experimental_createTRPCNextAppDirClient } from "@trpc/next/app-dir/client";

30

import { httpBatchLink } from "@trpc/client";

31

import type { AppRouter } from "./api/trpc";

32

33

// Create App Router client

34

const trpc = experimental_createTRPCNextAppDirClient<AppRouter>({

35

config() {

36

return {

37

links: [

38

httpBatchLink({

39

url: "/api/trpc",

40

}),

41

],

42

};

43

},

44

});

45

46

// Use in client components

47

async function UserProfile() {

48

const user = await trpc.user.getProfile.query({ userId: "123" });

49

return <div>Hello, {user.name}!</div>;

50

}

51

```

52

53

### App Router Server

54

55

Server-side utilities for Next.js App Router with revalidation and caching support.

56

57

```typescript { .api }

58

/**

59

* Creates server-side tRPC utilities for Next.js App Router

60

* @param opts - Configuration options for App Router server

61

* @returns Decorated router with server-side methods including revalidation

62

*/

63

function experimental_createTRPCNextAppDirServer<TRouter extends AnyRouter>(

64

opts: CreateTRPCNextAppRouterOptions<TRouter>

65

): NextAppDirDecorateRouterRecord<TRouter['_def']['_config']['$types'], TRouter['_def']['record']>;

66

67

type NextAppDirDecorateRouterRecord<TRoot extends AnyRootTypes, TRecord extends RouterRecord> = {

68

[TKey in keyof TRecord]: TRecord[TKey] extends infer $Value

69

? $Value extends AnyProcedure

70

? DecorateProcedureServer<$Value['_def']['type'], {

71

input: inferProcedureInput<$Value>;

72

output: inferTransformedProcedureOutput<TRoot, $Value>;

73

errorShape: TRoot['errorShape'];

74

transformer: TRoot['transformer'];

75

}>

76

: $Value extends RouterRecord

77

? NextAppDirDecorateRouterRecord<TRoot, $Value>

78

: never

79

: never;

80

};

81

```

82

83

**Usage Examples:**

84

85

```typescript

86

import { experimental_createTRPCNextAppDirServer } from "@trpc/next/app-dir/server";

87

import { experimental_nextCacheLink } from "@trpc/next/app-dir/links/nextCache";

88

import { appRouter } from "./api/root";

89

90

// Create server instance

91

const trpc = experimental_createTRPCNextAppDirServer<typeof appRouter>({

92

config() {

93

return {

94

links: [

95

experimental_nextCacheLink({

96

router: appRouter,

97

createContext: async () => ({}),

98

revalidate: 60, // Cache for 60 seconds

99

}),

100

],

101

};

102

},

103

});

104

105

// Use in server components

106

async function UserList() {

107

const users = await trpc.user.list.query();

108

return (

109

<div>

110

{users.map(user => (

111

<div key={user.id}>{user.name}</div>

112

))}

113

</div>

114

);

115

}

116

117

// Revalidate cache

118

async function RefreshUsers() {

119

await trpc.user.list.revalidate();

120

return <p>Users refreshed!</p>;

121

}

122

```

123

124

### Procedure Decoration

125

126

Each procedure in the App Router server gets decorated with additional methods.

127

128

```typescript { .api }

129

type DecorateProcedureServer<TType extends ProcedureType, TDef extends ResolverDef> =

130

TType extends 'query'

131

? {

132

/** Execute the query procedure */

133

query: Resolver<TDef>;

134

/** Revalidate cached results for this query */

135

revalidate: (input?: TDef['input']) => Promise<{ revalidated: false; error: string } | { revalidated: true }>;

136

}

137

: TType extends 'mutation'

138

? {

139

/** Execute the mutation procedure */

140

mutate: Resolver<TDef>;

141

}

142

: TType extends 'subscription'

143

? {

144

/** Execute the subscription procedure */

145

subscribe: Resolver<TDef>;

146

}

147

: never;

148

```

149

150

### Cache Revalidation Endpoint

151

152

HTTP endpoint handler for programmatic cache revalidation.

153

154

```typescript { .api }

155

/**

156

* HTTP endpoint handler for cache tag revalidation

157

* @param req - Request object with JSON body containing cacheTag

158

* @returns Response indicating revalidation success or failure

159

*/

160

async function experimental_revalidateEndpoint(req: Request): Promise<Response>;

161

```

162

163

**Usage Examples:**

164

165

```typescript

166

// In app/api/revalidate/route.ts

167

import { experimental_revalidateEndpoint } from "@trpc/next/app-dir/server";

168

169

export async function POST(req: Request) {

170

return experimental_revalidateEndpoint(req);

171

}

172

173

// Usage from client

174

async function revalidateUserData() {

175

const response = await fetch("/api/revalidate", {

176

method: "POST",

177

headers: { "Content-Type": "application/json" },

178

body: JSON.stringify({ cacheTag: "user.list" }),

179

});

180

181

const result = await response.json();

182

console.log("Revalidated:", result.revalidated);

183

}

184

```

185

186

## Advanced Usage

187

188

### Server Components with Error Boundaries

189

190

```typescript

191

import { Suspense } from "react";

192

import { ErrorBoundary } from "react-error-boundary";

193

194

async function ServerComponentWithTRPC() {

195

try {

196

const data = await trpc.posts.list.query();

197

return (

198

<div>

199

{data.map(post => (

200

<article key={post.id}>

201

<h2>{post.title}</h2>

202

<p>{post.content}</p>

203

</article>

204

))}

205

</div>

206

);

207

} catch (error) {

208

return <div>Failed to load posts</div>;

209

}

210

}

211

212

export default function PostsPage() {

213

return (

214

<ErrorBoundary fallback={<div>Something went wrong</div>}>

215

<Suspense fallback={<div>Loading posts...</div>}>

216

<ServerComponentWithTRPC />

217

</Suspense>

218

</ErrorBoundary>

219

);

220

}

221

```

222

223

### Streaming with Suspense

224

225

```typescript

226

import { Suspense } from "react";

227

228

async function UserProfile({ userId }: { userId: string }) {

229

const user = await trpc.user.getProfile.query({ userId });

230

return <div>Profile: {user.name}</div>;

231

}

232

233

async function UserPosts({ userId }: { userId: string }) {

234

const posts = await trpc.user.getPosts.query({ userId });

235

return (

236

<div>

237

{posts.map(post => (

238

<div key={post.id}>{post.title}</div>

239

))}

240

</div>

241

);

242

}

243

244

export default function UserPage({ params }: { params: { id: string } }) {

245

return (

246

<div>

247

<Suspense fallback={<div>Loading profile...</div>}>

248

<UserProfile userId={params.id} />

249

</Suspense>

250

<Suspense fallback={<div>Loading posts...</div>}>

251

<UserPosts userId={params.id} />

252

</Suspense>

253

</div>

254

);

255

}

256

```

257

258

## Types

259

260

```typescript { .api }

261

// Resolver function type for App Router procedures

262

interface Resolver<TDef extends ResolverDef> {

263

(input: TDef['input']): Promise<TDef['output']>;

264

}

265

266

// Resolver definition for type safety

267

interface ResolverDef {

268

input: any;

269

output: any;

270

transformer: boolean;

271

errorShape: any;

272

}

273

274

// Procedure types supported

275

type ProcedureType = 'query' | 'mutation' | 'subscription';

276

277

// Router record type

278

type RouterRecord = Record<string, AnyProcedure | RouterRecord>;

279

280

// Any procedure type

281

interface AnyProcedure {

282

_def: {

283

type: ProcedureType;

284

};

285

}

286

```