Routing Skill
This skill covers React Router 7 file-based routing in Storefront Next projects.
Overview
Storefront Next uses React Router 7 in framework mode with flat-routes file conventions. Route files live in src/routes/ and file names map directly to URL paths using dot (.) separators for nesting.
Route File Conventions
File Name to URL Mapping
| File Name | URL Path | Notes |
|-----------|----------|-------|
| _app.tsx | — | Layout route (wraps child routes) |
| _app._index.tsx | / | Home page (index of _app layout) |
| _app.product.$productId.tsx | /product/:productId | Dynamic parameter |
| _app.category.$categoryId.tsx | /category/:categoryId | Dynamic parameter |
| _app.cart.tsx | /cart | Static route |
| _app.account.tsx | /account | Layout for account pages |
| _app.account.orders.tsx | /account/orders | Nested under account layout |
Naming Rules
- Dots (
.) create path segments:_app.product.tsxmaps to/product $prefix marks dynamic parameters:$productIdbecomes:productId_prefix marks layout routes (pathless):_app.tsxwraps children without adding a URL segment_indexmatches the parent path exactly (index route)
See Route Conventions Reference for the complete naming rules.
Route Module Exports
Each route file can export these values:
loader — Server-side data loading
import { createApiClients } from '@/lib/api-clients';
import type { LoaderFunctionArgs } from 'react-router';
export function loader({ params, context }: LoaderFunctionArgs) {
const clients = createApiClients(context);
return {
product: clients.shopperProducts.getProduct({
params: { path: { id: params.productId } }
}).then(({ data }) => data),
};
}
action — Handle mutations (form submissions)
import { data, redirect } from 'react-router';
import type { ActionFunctionArgs } from 'react-router';
export async function action({ request, context }: ActionFunctionArgs) {
const formData = await request.formData();
// Handle mutation...
return data({ success: true });
}
default — Page component
Components receive loaderData as props from the framework:
import { Suspense } from 'react';
import { Await } from 'react-router';
import { SeoMeta } from '@/components/seo-meta';
export default function CategoryPage({ loaderData }: { loaderData: CategoryPageData }) {
// `category` is already resolved (awaited in loader)
// `products` is a Promise (streamed)
return (
<>
<SeoMeta
title={loaderData.category.name}
description={loaderData.category.pageDescription}
/>
<h1>{loaderData.category.name}</h1>
<Suspense fallback={<ProductGridSkeleton />}>
<Await resolve={loaderData.products}>
{(products) => <ProductGrid products={products} />}
</Await>
</Suspense>
</>
);
}
For SEO metadata, use the <SeoMeta /> component inside your page component (not a meta export).
Layout Routes
Layout routes wrap child routes with shared UI (navigation, footer, etc.):
// src/routes/_app.tsx — Main app layout
import { Outlet } from 'react-router';
export default function AppLayout() {
return (
<div>
<Header />
<main>
<Outlet /> {/* Child routes render here */}
</main>
<Footer />
</div>
);
}
Adding a New Page
-
Create a route file in
src/routes/:src/routes/_app.wishlist.tsx → /wishlist -
Export a
loaderfor data anddefaultfor the component:import { createApiClients } from '@/lib/api-clients'; import { SeoMeta } from '@/components/seo-meta'; import type { LoaderFunctionArgs } from 'react-router'; type WishlistPageData = { wishlist: Promise<Wishlist>; }; export function loader({ context }: LoaderFunctionArgs): WishlistPageData { const clients = createApiClients(context); return { wishlist: clients.shopperCustomers.getWishlist({...}).then(({ data }) => data), }; } export default function WishlistPage({ loaderData }: { loaderData: WishlistPageData }) { return ( <> <SeoMeta title="My Wishlist" /> <Suspense fallback={<WishlistSkeleton />}> <Await resolve={loaderData.wishlist}> {(wishlist) => <div>{/* Render wishlist */}</div>} </Await> </Suspense> </> ); }
Resource Routes
Routes that return data (no UI component):
// src/routes/resource.api.client.$resource.tsx
// Used by useScapiFetcher to execute SCAPI calls server-side
export function loader({ params, context }: LoaderFunctionArgs) {
// Decode params, call SCAPI, return JSON
}
Related Skills
storefront-next:sfnext-data-fetching- What happens inside loader and action functionsstorefront-next:sfnext-components- Building page components with createPagestorefront-next:sfnext-page-designer- Page Designer routes with regions
Reference Documentation
- Route Conventions Reference - Complete naming rules and examples