Data Loading

Phyre uses React Router's loader pattern for server-side data fetching.

Basic Loader

Export an async loader function from any route to fetch data before rendering:

jsx
// src/client/routes/posts/index.jsx import { useLoaderData, Link } from 'react-router'; // Loader runs on the server (SSR) or client (navigation) export async function loader() { const response = await fetch('http://localhost:3000/api/posts'); const data = await response.json(); return data; } export default function Posts() { const posts = useLoaderData(); return ( <div> <h1>Posts</h1> {posts.data.map(post => ( <Link key={post.id} to={`/posts/${post.id}`}> {post.title} </Link> ))} </div> ); }

Loader with Params

Access route parameters in the loader:

jsx
// src/client/routes/posts/[id].jsx import { useLoaderData } from 'react-router'; export async function loader({ params }) { const response = await fetch(`http://localhost:3000/api/posts/${params.id}`); const data = await response.json(); return data; } export default function PostDetail() { const post = useLoaderData(); return ( <div> <h1>{post.title}</h1> <p>{post.content}</p> </div> ); }

Loader with Request

Access the full request object:

jsx
export async function loader({ request, params }) { const url = new URL(request.url); const searchQuery = url.searchParams.get('q'); const response = await fetch( `http://localhost:3000/api/search?q=${searchQuery}` ); return response.json(); }

Multiple Data Sources

Fetch from multiple APIs in parallel:

jsx
export async function loader() { const [posts, categories, user] = await Promise.all([ fetch('http://localhost:3000/api/posts').then(r => r.json()), fetch('http://localhost:3000/api/categories').then(r => r.json()), fetch('http://localhost:3000/api/user').then(r => r.json()) ]); return { posts, categories, user }; } export default function Dashboard() { const { posts, categories, user } = useLoaderData(); // ... }

Error Handling

Handle errors in loaders:

jsx
export async function loader({ params }) { const response = await fetch(`http://localhost:3000/api/posts/${params.id}`); if (!response.ok) { throw new Response('Not Found', { status: 404 }); } return response.json(); } // Optional: Define error boundary export function ErrorBoundary() { return ( <div> <h1>Error loading post</h1> <p>The post could not be found.</p> </div> ); }

Best Practices

  • Always return data - Don't return undefined
  • Handle errors - Use try/catch or check response.ok
  • Use absolute URLs - Full URL in loaders (for SSR)
  • Keep loaders fast - Heavy logic should be in API routes
  • Parallel fetching - Use Promise.all for multiple requests

Note: Loaders run on both server (SSR) and client (navigation). Make sure your code works in both environments.