Agent Skills: Storefront Next 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.

UncategorizedID: SalesforceCommerceCloud/b2c-developer-tooling/sfnext-routing

Install this agent skill to your local

pnpm dlx add-skill https://github.com/SalesforceCommerceCloud/b2c-developer-tooling/tree/HEAD/skills/storefront-next/skills/sfnext-routing

Skill Files

Browse the full folder contents for sfnext-routing.

Download Skill

Loading file tree…

skills/storefront-next/skills/sfnext-routing/SKILL.md

Skill Metadata

Name
sfnext-routing
Description
>-

Storefront Next Routing

Storefront Next uses React Router 7 framework mode with file-based routes. Every route module lives in src/routes/; the file name is the URL. Your project also ships docs/README-MULTI-SITE.md (URL prefix, locale and site detection) and docs/README-SEO.md (titles, meta tags, canonical URLs); read those for depth.

How routes are discovered

src/routes.ts imports flatRoutes from @salesforce/storefront-next-runtime/routing (not @react-router/fs-routes). It is a drop-in wrapper that:

  1. scans src/routes/ (test files are ignored),
  2. merges routes from src/extensions/<name>/routes/ (see storefront-next:sfnext-extensions),
  3. wraps every route in the app.url.prefix pattern (default /:siteId/:localeId) through src/app-wrapper.tsx, and applies app.url.seoRoutes aliases to the product and category splats.

The two SLAS callback routes (resource.slas-reset-password-callback.ts, resource.slas-passwordless-login-callback.ts) are deliberately excluded from discovery in src/routes.ts and registered by hand at the bare paths /reset-password-callback and /passwordless-login-callback, outside the prefix, so a single callback URL works for all sites. If you add another server-to-server callback that cannot carry a site prefix, follow the same pattern in src/routes.ts.

Route tree (what exists)

| Pattern | Files | Notes | |---|---|---| | _app layout | _app.tsx | header/footer shell; loads navigation; shouldRevalidate returns false | | Home, search, cart, wishlist | _app._index.tsx, _app.search.tsx, _app.cart.tsx, _app.wishlist.tsx, _app.about-us.tsx | | | Product detail | _app.p.$.tsx | splat; URL shape is build-time configurable (url.seoRoutes) | | Category / listing | _app.c.$.tsx | splat; URL shape is build-time configurable (url.seoRoutes) | | Account | _app.account.tsx (layout + auth guard), _app.account.overview, .orders, .orders.$orderNo, .addresses, .payment-methods, .wishlist, .passkeys, .store-preferences | | | Orders | _app.order-confirmation.$orderNo.tsx, _app.order-lookup.* (results.$orderNo, verify.$orderNo) | param is $orderNo | | _checkout layout | _checkout.tsx, _checkout.checkout.tsx | checkout chrome | | _empty layout | _empty.tsx, _empty.login, .signup, .forgot-password, .reset-password, .logout, .maintenance, .oauth2.jwks, .preview.component, _empty.$.tsx (catch-all) | no header/footer; there is no _auth layout | | Mutations | action.*.ts(x) (cart, wishlist, checkout, OTP, passkey, consent, site context...) | no UI; see storefront-next:sfnext-data-fetching | | Data endpoints | resource.*.ts(x) (recommendations, basket-products, category-products, analytics-proxy, resource.api.client.$resource.ts...) | no UI |

Naming rules (flat routes): . becomes /, $name is a param, $ alone is a splat, a leading _ segment is a pathless layout, _index is the index route. Pick the layout by prefix: _app. for standard pages, _checkout. for checkout, _empty. for header-less pages.

Add a page

// src/routes/_app.shipping-policy.tsx  ->  /shipping-policy
import type { Route } from './+types/_app.shipping-policy';
import { SeoMeta } from '@/components/seo-meta';
import { Link } from '@/components/link';
import { routes } from '@/route-paths';

export async function loader({ request }: Route.LoaderArgs) {
    return { pageUrl: new URL(request.url).href };
}

export default function ShippingPolicy({ loaderData }: Route.ComponentProps) {
    return (
        <>
            <SeoMeta title="Shipping policy" description="How we ship." openGraph={{ url: loaderData.pageUrl }} />
            <h1>Shipping policy</h1>
            <Link to={routes.cart}>Back to cart</Link>
        </>
    );
}

Checklist for a new page:

  1. Create the file under src/routes/ with the right layout prefix.
  2. Import types from ./+types/<file-name> (Route.LoaderArgs, Route.ComponentProps, Route.ActionArgs). These are generated by pnpm typecheck (runs react-router typegen) and pnpm dev; run one if the import is unresolved.
  3. Add an entry to routes (page) or resourceRoutes (action/resource) in src/route-paths.ts. That file is the single source of truth for navigable paths. Keep it in sync when you rename or delete route files.
  4. Render <SeoMeta> (and <JsonLd> from @/components/json-ld for structured data) inside the component. React 19 hoists these tags to <head> and they work with streaming. Use meta exports only when a route already does.
  5. Add locale strings for user-facing text (storefront-next:sfnext-i18n) and a test next to the route (storefront-next:sfnext-testing).

Links and navigation: always use the project wrappers

import { Link, NavLink } from '@/components/link';
import { useNavigate } from '@/hooks/use-navigate';
import { href } from 'react-router';
import { routes, routeHref, resourceRoutes } from '@/route-paths';

<Link to={routes.cart}>Cart</Link>
<Link to={routeHref(routes.accountOrderDetail, { orderNo })}>Order</Link>

Link, NavLink and useNavigate from @/components/link and @/hooks/use-navigate apply the site/locale prefix (buildUrl). The React Router originals compile but silently produce unprefixed URLs that break multi-site routing. AGENTS.md makes this a hard rule.

For product and category URLs use the helpers in @/route-paths (createProductUrl, createCategoryUrl, createCategoryNavigationUrl) with the SEO URL context so links follow url.seoRoutes. In server code (redirects), use buildUrlFromContext(routes.login, context) from @/lib/url.server:

import { redirect } from 'react-router';
import { routes } from '@/route-paths';
import { buildUrlFromContext } from '@/lib/url.server';

throw redirect(buildUrlFromContext(routes.login, context));

Form and fetcher action targets come from resourceRoutes (for example resourceRoutes.cartItemAdd), which are excluded from the site prefix via app.url.excludeRoutes in config.server.ts (['/resource/**', '/action/**'] by default). A new action.* or resource.* route needs no prefix handling; a new page route gets it automatically.

URL configuration

app.url in config.server.ts (prefix, excludeRoutes and seoRoutes are protected config paths: set them in the file and rebuild, they cannot be overridden with PUBLIC__ env vars; see storefront-next:sfnext-configuration) controls prefix, excludeRoutes, search-param mode, detection and seoRoutes (configurable product and category URL grammars). Changing the prefix or seoRoutes affects every link; follow docs/README-MULTI-SITE.md in your project rather than editing ad hoc.

Protecting routes

There is no _auth layout. Guard in the loader: read the session with getAuth(context) from @/middlewares/auth.server and throw redirect(...) to routes.login (see _app.account.tsx and _app.wishlist.tsx). Auth details: storefront-next:sfnext-authentication.

Rules to keep in mind

  • Only server loader and action exports are allowed; no clientLoader/clientAction.
  • Throw new Response('...', { status: 404 }) for missing content so the right HTTP status reaches crawlers.
  • Resource routes called from the browser should reject cross-origin requests (see resource.recommendations.ts for the pattern).
  • Deeper references: ROUTE-CONVENTIONS.md for exports and naming, URLS-AND-SEO-ROUTES.md for prefixes and SEO aliases.

Finding more

Read AGENTS.md in your project for the doc index. For other framework questions use b2c docs search "storefront next routing" (or the docs_search MCP tool).

Related Skills

  • storefront-next:sfnext-data-fetching - loaders, actions, fetchers
  • storefront-next:sfnext-revalidation - shouldRevalidate policy per route
  • storefront-next:sfnext-seo - canonical URLs, structured data, sitemaps
  • storefront-next:sfnext-page-designer - @PageType and regions on routes
  • storefront-next:sfnext-extensions - routes contributed by extensions
  • storefront-next:sfnext-authentication - sessions and login flows
  • storefront-next:sfnext-overview - architecture map