Summary
Create responses that contain headers and status without forcing serialization into an actual Response object.
The data() function allows you to attach metadata like HTTP headers and status codes to your loader/action data while keeping type safety and avoiding premature serialization.
Signature
function data<D>(
data: D,
init?: number | ResponseInit
): DataWithResponseInit<D>
Parameters
The data to be included in the response. Can be any serializable value including objects, arrays, primitives, etc.
Either a status code number or a ResponseInit object containing:
status - HTTP status code (e.g., 200, 201, 404)
statusText - Status text message
headers - Response headers object or Headers instance
If a number is provided, it’s used as the status code.
Returns
A DataWithResponseInit instance containing the data and response initialization options.
Examples
Basic usage
Return data without any special headers or status:
export async function loader({ params }: Route.LoaderArgs) {
const user = await getUser(params.id);
return { user }; // Simple return, no need for data()
}
With status code
import { data } from "react-router";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const item = await createItem(formData);
// Return 201 Created status
return data(item, 201);
}
import { data } from "react-router";
export async function loader({ params }: Route.LoaderArgs) {
const user = await getUser(params.id);
return data(
{ user },
{
headers: {
"Cache-Control": "max-age=300, s-maxage=3600",
"X-Custom-Header": "value",
},
}
);
}
With status and headers
import { data } from "react-router";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const item = await createItem(formData);
return data(
{ item },
{
status: 201,
headers: {
"X-Custom-Header": "value",
},
}
);
}
Common Use Cases
Control how browsers and CDNs cache your responses:
import { data } from "react-router";
export async function loader() {
const posts = await getPosts();
return data(
{ posts },
{
headers: {
"Cache-Control": "public, max-age=300, s-maxage=3600",
},
}
);
}
Error responses with status codes
import { data } from "react-router";
export async function loader({ params }: Route.LoaderArgs) {
const user = await getUser(params.id);
if (!user) {
throw data(
{ error: "User not found" },
{ status: 404, statusText: "Not Found" }
);
}
return { user };
}
Setting cookies
import { data } from "react-router";
export async function action({ request }: Route.ActionArgs) {
const session = await getSession(request);
session.set("userId", "123");
return data(
{ success: true },
{
headers: {
"Set-Cookie": await commitSession(session),
},
}
);
}
Content negotiation
import { data } from "react-router";
export async function loader({ request }: Route.LoaderArgs) {
const items = await getItems();
return data(
{ items },
{
headers: {
"Content-Type": "application/json; charset=utf-8",
"Vary": "Accept",
},
}
);
}
import { data } from "react-router";
export async function action({ request }: Route.ActionArgs) {
const result = await processRequest(request);
return data(
result,
{
headers: {
"X-RateLimit-Limit": "100",
"X-RateLimit-Remaining": "99",
"X-RateLimit-Reset": "1640000000",
},
}
);
}
Type Safety
The data() function preserves TypeScript types:
interface User {
id: string;
name: string;
}
export async function loader() {
const user: User = await getUser();
// Type is preserved: DataWithResponseInit<{ user: User }>
return data({ user }, 200);
}
Comparison with Response
You can also return a native Response object:
// Using data() - keeps type safety
import { data } from "react-router";
export async function loader() {
return data({ message: "Hello" }, 200);
}
// Using Response - loses type safety
export async function loader() {
return new Response(
JSON.stringify({ message: "Hello" }),
{
status: 200,
headers: { "Content-Type": "application/json" },
}
);
}
Use data() when you want type safety and automatic serialization. Use Response when you need fine-grained control over the response format.
redirect - Create redirect responses
json - Deprecated v6 equivalent
Notes
- You don’t need to use
data() if you’re just returning plain data without custom headers/status
- The data is not serialized until it’s sent to the client, allowing for efficient server-side processing
- Headers are merged with any headers set by middleware or the framework
- Status codes should follow HTTP standards for proper client handling