sfnext-routing
Implement file-based routing in Storefront Next with React Router 7. Use when adding new pages, creating layout routes, defining route parameters, or understanding route module exports (loader, action, component, meta). Covers flat-routes conventions, nested layouts, and the _app prefix.
Skill body
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