0
# Data Response Utilities
1
2
Server runtime utilities for creating responses, redirects, and handling deferred data in Remix loader and action functions.
3
4
## Capabilities
5
6
### JSON Response
7
8
Create JSON responses with proper headers and serialization.
9
10
```typescript { .api }
11
/**
12
* Creates a Response with JSON data and appropriate headers
13
* Automatically serializes data and sets Content-Type header
14
* @param data - Data to serialize as JSON
15
* @param init - Optional response initialization options
16
* @returns Response object with JSON data
17
*/
18
function json<Data>(data: Data, init?: ResponseInit): Response;
19
```
20
21
**Usage Examples:**
22
23
```typescript
24
import { json } from "@remix-run/react";
25
import type { LoaderFunctionArgs } from "@remix-run/node";
26
27
// Basic JSON response
28
export async function loader({ params }: LoaderFunctionArgs) {
29
const user = await getUser(params.userId);
30
31
if (!user) {
32
throw json({ error: "User not found" }, { status: 404 });
33
}
34
35
return json({
36
user,
37
lastUpdated: new Date(),
38
preferences: user.preferences,
39
});
40
}
41
42
// JSON response with custom headers
43
export async function loader() {
44
const data = await getPublicData();
45
46
return json(data, {
47
headers: {
48
"Cache-Control": "public, max-age=3600",
49
"X-Custom-Header": "value",
50
},
51
});
52
}
53
54
// JSON error response
55
export async function action({ request }: ActionFunctionArgs) {
56
const formData = await request.formData();
57
58
try {
59
const result = await processForm(formData);
60
return json({ success: true, result });
61
} catch (error) {
62
return json(
63
{
64
success: false,
65
error: error.message,
66
field: "email"
67
},
68
{ status: 400 }
69
);
70
}
71
}
72
```
73
74
### Redirect Response
75
76
Create redirect responses for navigation and flow control.
77
78
```typescript { .api }
79
/**
80
* Creates a redirect Response to the specified URL
81
* @param url - URL to redirect to
82
* @param init - Optional response initialization options
83
* @returns Response object with redirect status and Location header
84
*/
85
function redirect(url: string, init?: ResponseInit): Response;
86
87
/**
88
* Creates a redirect Response that reloads the entire document
89
* Forces a full page reload instead of client-side navigation
90
* @param url - URL to redirect to
91
* @param init - Optional response initialization options
92
* @returns Response object with redirect and document reload
93
*/
94
function redirectDocument(url: string, init?: ResponseInit): Response;
95
96
/**
97
* Creates a redirect Response that replaces the current entry in the history stack
98
* Uses replace navigation instead of pushing a new entry
99
* @param url - URL to redirect to
100
* @param init - Optional response initialization options (defaults to 302)
101
* @returns Response object with redirect status and Location header
102
*/
103
function replace(url: string, init?: ResponseInit): Response;
104
```
105
106
**Usage Examples:**
107
108
```typescript
109
import { redirect, redirectDocument, replace } from "@remix-run/react";
110
import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
111
112
// Basic redirect
113
export async function loader({ request }: LoaderFunctionArgs) {
114
const user = await getCurrentUser(request);
115
116
if (!user) {
117
return redirect("/login");
118
}
119
120
return json({ user });
121
}
122
123
// Redirect with custom status
124
export async function action({ request }: ActionFunctionArgs) {
125
const formData = await request.formData();
126
const result = await createPost(formData);
127
128
// Redirect to the new post with 201 status
129
return redirect(`/posts/${result.id}`, { status: 201 });
130
}
131
132
// Document redirect (full page reload)
133
export async function action({ request }: ActionFunctionArgs) {
134
await processPayment(request);
135
136
// Force full page reload for payment confirmation
137
return redirectDocument("/payment/success");
138
}
139
140
// Replace redirect (replaces current history entry)
141
export async function action({ request }: ActionFunctionArgs) {
142
const formData = await request.formData();
143
const result = await updateProfile(formData);
144
145
// Replace current URL instead of adding to history
146
return replace("/profile", { status: 302 });
147
}
148
149
// Conditional redirect
150
export async function loader({ request, params }: LoaderFunctionArgs) {
151
const user = await getCurrentUser(request);
152
const post = await getPost(params.postId);
153
154
if (!post) {
155
throw json({ error: "Post not found" }, { status: 404 });
156
}
157
158
if (post.private && post.authorId !== user?.id) {
159
return redirect("/unauthorized");
160
}
161
162
return json({ post, user });
163
}
164
```
165
166
### Deferred Response
167
168
Create responses with deferred data for streaming and progressive loading.
169
170
```typescript { .api }
171
/**
172
* Creates a Response with deferred data for streaming
173
* Allows some data to load immediately while other data streams in later
174
* @param data - Object containing immediate and deferred data
175
* @param init - Optional response initialization options
176
* @returns Response object with streaming deferred data
177
*/
178
function defer<Data extends Record<string, unknown>>(
179
data: Data,
180
init?: ResponseInit
181
): Response;
182
```
183
184
**Usage Examples:**
185
186
```typescript
187
import { defer, json } from "@remix-run/react";
188
import { Await, useLoaderData } from "@remix-run/react";
189
import { Suspense } from "react";
190
import type { LoaderFunctionArgs } from "@remix-run/node";
191
192
// Deferred data loading
193
export async function loader({ params }: LoaderFunctionArgs) {
194
const user = await getUser(params.userId); // Fast query
195
196
// Defer slow queries
197
const posts = getUserPosts(params.userId); // Don't await
198
const analytics = getUserAnalytics(params.userId); // Don't await
199
200
return defer({
201
user, // Immediate data
202
posts, // Deferred promise
203
analytics, // Deferred promise
204
});
205
}
206
207
// Component using deferred data
208
export default function UserProfile() {
209
const { user, posts, analytics } = useLoaderData<typeof loader>();
210
211
return (
212
<div>
213
<h1>{user.name}</h1>
214
215
<Suspense fallback={<div>Loading posts...</div>}>
216
<Await resolve={posts}>
217
{(resolvedPosts) => (
218
<PostsList posts={resolvedPosts} />
219
)}
220
</Await>
221
</Suspense>
222
223
<Suspense fallback={<div>Loading analytics...</div>}>
224
<Await resolve={analytics}>
225
{(resolvedAnalytics) => (
226
<AnalyticsDashboard data={resolvedAnalytics} />
227
)}
228
</Await>
229
</Suspense>
230
</div>
231
);
232
}
233
234
// Mixed immediate and deferred data
235
export async function loader() {
236
const criticalData = await getCriticalData(); // Must load first
237
const slowData = getSlowData(); // Defer this
238
239
return defer({
240
critical: criticalData,
241
slow: slowData,
242
metadata: { loadTime: Date.now() },
243
});
244
}
245
```
246
247
### Replace Response
248
249
Create responses that replace the current history entry.
250
251
```typescript { .api }
252
/**
253
* Creates a response that replaces the current history entry
254
* Useful for preventing back button navigation to intermediate states
255
* @param url - URL to replace with
256
* @param init - Optional response initialization options
257
* @returns Response object with replace behavior
258
*/
259
function replace(url: string, init?: ResponseInit): Response;
260
```
261
262
**Usage Examples:**
263
264
```typescript
265
import { replace, json } from "@remix-run/react";
266
import type { ActionFunctionArgs } from "@remix-run/node";
267
268
// Replace after form submission
269
export async function action({ request }: ActionFunctionArgs) {
270
const formData = await request.formData();
271
272
if (formData.get("step") === "confirm") {
273
await processOrder(formData);
274
// Replace the confirmation step in history
275
return replace("/order/success");
276
}
277
278
return json({ step: "confirm" });
279
}
280
```
281
282
### Generic Data Response
283
284
Create flexible data responses for various content types.
285
286
```typescript { .api }
287
/**
288
* Creates a generic data response with flexible content handling
289
* Supports various data types and custom serialization
290
* @param data - Data to include in the response
291
* @param init - Optional response initialization options
292
* @returns Response object with the provided data
293
*/
294
function data<Data>(data: Data, init?: ResponseInit): Response;
295
```
296
297
**Usage Examples:**
298
299
```typescript
300
import { data } from "@remix-run/react";
301
302
// Custom response with specific content type
303
export async function loader() {
304
const csvData = await generateCSVReport();
305
306
return data(csvData, {
307
headers: {
308
"Content-Type": "text/csv",
309
"Content-Disposition": "attachment; filename=report.csv",
310
},
311
});
312
}
313
314
// Binary data response
315
export async function loader({ params }: LoaderFunctionArgs) {
316
const imageBuffer = await getImage(params.imageId);
317
318
return data(imageBuffer, {
319
headers: {
320
"Content-Type": "image/jpeg",
321
"Cache-Control": "public, max-age=86400",
322
},
323
});
324
}
325
```
326
327
## Type Definitions
328
329
### Response Initialization
330
331
```typescript { .api }
332
interface ResponseInit {
333
/** HTTP status code */
334
status?: number;
335
/** HTTP status text */
336
statusText?: string;
337
/** Response headers */
338
headers?: HeadersInit;
339
}
340
341
type HeadersInit = Headers | string[][] | Record<string, string>;
342
```
343
344
### Deferred Data Type
345
346
```typescript { .api }
347
/**
348
* Type for deferred data objects
349
* Keys can contain immediate values or promises that resolve later
350
*/
351
type DeferredData<T extends Record<string, unknown>> = {
352
[K in keyof T]: T[K] | Promise<T[K]>;
353
};
354
```
355
356
## Implementation Notes
357
358
- **Streaming**: Deferred responses enable streaming data to the client as it becomes available
359
- **Performance**: Immediate data renders instantly while slow queries complete in the background
360
- **Error Handling**: Deferred promises can reject and be caught by error boundaries
361
- **SEO**: Search engines receive immediate data for indexing while deferred data loads progressively
362
- **Type Safety**: All response utilities maintain full TypeScript type safety
363
- **Headers**: Custom headers can be set for caching, content type, and security policies
364
- **Status Codes**: HTTP status codes can be customized for proper REST API compliance