Skip to main content

sfnext-data-fetching

Load and mutate data in a Storefront Next project: route loaders that call SCAPI through createApiClients from @/lib/api-clients.server, critical versus streamed (deferred) data, createBasketAction and BasketAction for cart actions, createActionError and ErrorCode, data() responses, useFetcher and the useScapiFetcher hook with its server allowlist, request-scoped dedupe and MRT timeouts, non-personalized SCAPI responses, shopper context, and custom API clients. Use for "fetch products in a loader", "stream data", "add a cart action", "call SCAPI from a component", "resource route", "NormalizedApiError", or "loader blocks the page". Do not use for route files or links (use `storefront-next:sfnext-routing`), when loaders re-run after actions (use `storefront-next:sfnext-revalidation`), Suspense/LCP tuning (use `storefront-next:sfnext-performance`), or defining SCAPI custom APIs (use `storefront-next:sfnext-scapi`).

Quellinformationen

Repository
SalesforceCommerceCloud/b2c-developer-tooling
Letzte Quellaktivität
4. Oktober 2026 um 16:40
Erkannte Sprache von SKILL.md
Englisch
Sterne
56
Forks
22

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
6 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
sfnext-data-fetching
description
Load and mutate data in a Storefront Next project: route loaders that call SCAPI through createApiClients from @/lib/api-clients.server, critical versus streamed (deferred) data, createBasketAction and BasketAction for cart actions, createActionError and ErrorCode, data() responses, useFetcher and the useScapiFetcher hook with its server allowlist, request-scoped dedupe and MRT timeouts, non-personalized SCAPI responses, shopper context, and custom API clients. Use for "fetch products in a loader", "stream data", "add a cart action", "call SCAPI from a component", "resource route", "NormalizedApiError", or "loader blocks the page". Do not use for route files or links (use `storefront-next:sfnext-routing`), when loaders re-run after actions (use `storefront-next:sfnext-revalidation`), Suspense/LCP tuning (use `storefront-next:sfnext-performance`), or defining SCAPI custom APIs (use `storefront-next:sfnext-scapi`).
# Storefront Next Data Fetching All data comes from the server. Loaders and actions run on the server with the shopper's session; the browser never calls SCAPI directly. Your project ships `docs/README-DATA.md`, `docs/README-SUSPENSE.md`, `docs/README-SCAPI-NON-PERSONALIZED-RESPONSES.md` and `docs/README-SHOPPER-CONTEXT.md`, plus the "Performance & Data Rules" in `AGENTS.md`; this skill is the quick path, those are authoritative. ## The rules (same as AGENTS.md) 1. Fetch on the server in `loader` (reads) and `action` (writes). There is no `clientLoader` or `clientAction`. 2. Classify every piece of data: - **Critical** (needed for SEO, LCP, layout stability or the HTTP status): `await` it in the loader. - **Non-critical** (below the fold, recommendations, reviews, secondary panels): start the request, do not await it, and return the unresolved promise so it streams. - **Interaction-driven** (only after a click, hover or input): fetch on demand with a fetcher. 3. Never block a loader on non-critical data. 4. Pass the promise through `loaderData` and read it under its own `<Suspense>` with `use()` or `<Await>`. Keep the promise identity stable (compose it in the loader, never `Promise.all` or `.then` in render). 5. Export `shouldRevalidate` on routes whose loaders depend on URL filters (see `storefront-next:sfnext-revalidation`). ## Loader with critical and streamed data ```tsx import { Suspense } from 'react'; import { Await } from 'react-router'; import type { Route } from './+types/_app.example'; import { createApiClients } from '@/lib/api-clients.server'; import { fetchProductById } from '@/lib/api/products.server'; import { NormalizedApiError } from '@/lib/api/normalized-api-error'; export async function loader({ context, params }: Route.LoaderArgs) { const clients = createApiClients(context); // Non-critical: start now, do not await. const related = clients.shopperSearch .productSearch({ params: { query: { q: 'shirt', limit: 8 } } }) .then(({ data }) => data.hits ?? []); // Critical: needed for status, SEO and LCP. try { const product = await fetchProductById(context, params.id ?? '', { expand: ['images', 'prices'] }); if (!product) throw new Response('Not found', { status: 404 }); return { product, related }; } catch (e) { if (e instanceof NormalizedApiError && e.status) throw new Response(e.message, { status: e.status }); throw e; } } export default function Example({ loaderData }: Route.ComponentProps) { return ( <> <h1>{loaderData.product.name}</h1> <Suspense fallback={<div className="h-40" aria-busy="true" />}> <Await resolve={loaderData.related}>{(hits) => <ul>{hits.map((h) => <li key={h.productId}>{h.productName}</li>)}</ul>}</Await> </Suspense> </> ); } ``` Real precedent: `src/routes/_app.p.$.tsx` awaits the product (404 becomes `throw new Response(..., { status: 404 })`) and streams the Page Designer page, schema and extras; `src/components/product-grid/deferred.tsx` is the streamed-grid pattern. ## SCAPI clients `createApiClients(context)` from `@/lib/api-clients.server` returns `AppClients`: every generated Shopper API client (`shopperProducts`, `shopperSearch`, `shopperBasketsV2`, `shopperCustomers`, `shopperOrders`, `shopperLogin`, and more) plus generated clients for your custom APIs (`@/scapi/custom-clients`). Call shape: ```ts const { data } = await clients.shopperBasketsV2.addItemToBasket({ params: { path: { basketId }, query: {} }, body: [{ productId, quantity: 1 }], // body is a sibling of params; this endpoint takes an array }); ``` Results are `{ data }`; failures throw. Prefer the thin wrappers in `src/lib/api/*.server.ts` (`fetchProductById`, `fetchProductsByIds` which chunks to the 24-id limit, category, search, order, customer and basket helpers): they log and rethrow as `NormalizedApiError` (`@/lib/api/normalized-api-error`, with `.status` and `.cause`). Add new wrappers there rather than calling clients inline in many routes. The clients already wrap `fetch` with request-scoped GET/HEAD dedupe (identical calls in one request share one response; any mutation clears the cache), a hard timeout from the `MRT_REQUEST_TIMEOUT` environment variable when set, and a health observer that logs 429 and load-status headers. Do not re-implement them. More: [API-CLIENTS.md](references/API-CLIENTS.md). Trim payloads: pass only the `expand`/`select` values you render. Availability is cached about 60 seconds and prices/promotions about 15 minutes, so short-TTL data is a good candidate for streaming. Shopper-agnostic responses can be cached using the policy in `src/lib/scapi/non-personalized-response-policy.server.ts` (see the shipped README-SCAPI-NON-PERSONALIZED-RESPONSES); request `personalized: 'none'` only where the response truly is the same for all shoppers, as the navigation loader in `_app.tsx` does. ## Mutations: actions Prefer an `action.*` route and a `<Form>` (navigating) or `useFetcher` (non-navigating). Basket mutations use the factory, which loads the basket, parses `FormData`, catches errors and wraps the result: ```ts // src/routes/action.example-remove-item.ts import { data } from 'react-router'; import { BasketAction, createBasketAction } from '@/lib/cart/basket-action.server'; import { createActionError } from '@/lib/action-error-helpers.server'; import { ErrorCode } from '@/lib/error-codes'; export const action = createBasketAction( { method: 'POST', action: BasketAction.CartItemRemove, parse: (fd) => ({ itemId: String(fd.get('itemId') ?? '') }) }, async ({ input, basketId, clients }) => { if (!input.itemId) { return data({ success: false, error: createActionError({ code: ErrorCode.REQUIRED_FIELD, message: 'itemId is required' }) }, { status: 400 }); } const { data: basket } = await clients.shopperBasketsV2.removeItemFromBasket({ params: { path: { basketId, itemId: input.itemId } } }); return basket; // factory returns { success: true, basket } and syncs the basket resource } ); ``` Return the updated `Basket` for success. Return `data(payload, { status })` for validation errors (use `data()` from `react-router`, not `Response.json`). Thrown SCAPI 4xx errors pass their status through; others become 500. Available `BasketAction` values are listed in `src/lib/cart/basket-action.server.ts`. See the existing `action.cart-item-*.tsx` files. Details and non-basket actions: [ACTIONS.md](references/ACTIONS.md). ## Fetching from components - Use `useFetcher` against your own `action.*`/`resource.*` routes (`resourceRoutes` in `@/route-paths`). - Use `useScapiFetcher(client, method, { params, body })` from `@/hooks/use-scapi-fetcher` for a small allowlisted set of Shopper calls without writing a route. It returns `.load()`, `.submit(payload)`, `.data`, `.errors`, `.success`. The server enforces an allowlist in `src/lib/scapi/resource-policy.ts` (for example `shopperProducts.getProduct`, `shopperBasketsV2.getBasket`, `shopperSearch.getSearchSuggestions`, and selected `shopperCustomers` address and profile mutations). Anything else needs a dedicated route. See [SCAPI-FETCHER.md](references/SCAPI-FETCHER.md). - Do not fetch in `useEffect` on mount what the loader could fetch (see the performance review checklist in `storefront-next:sfnext-performance`). - Fetchers and raw `fetch` calls that do not go through a submission do not trigger revalidation; actions do, for every active loader. ## Checklist 1. Can the loader fetch this at request time? If yes, do it there. 2. Is it critical? `await`; otherwise return the promise with its own Suspense and a sized skeleton. 3. Did you request only the fields you render? 4. Mutation errors: `createActionError` with an `ErrorCode`, return with `data(..., { status })`. 5. New shared fetch logic goes in `src/lib/api/*.server.ts` with a test (`storefront-next:sfnext-testing`). 6. Run `pnpm typecheck` and `pnpm test`. Deeper: [LOADERS.md](references/LOADERS.md). ## Finding more Open `AGENTS.md` ("Key Documentation") and `docs/README-DATA.md` in your project. Search product docs with `b2c docs search "<term>"` or the `docs_search` MCP tool. ## Related Skills - `storefront-next:sfnext-routing` - route files and links - `storefront-next:sfnext-revalidation` - avoid wasted loader re-runs after actions - `storefront-next:sfnext-performance` - Suspense placement, LCP, review checklist - `storefront-next:sfnext-state-management` - basket provider and client state - `storefront-next:sfnext-scapi` - SCAPI access and custom APIs - `storefront-next:sfnext-authentication` - sessions behind loaders - `b2c-cli:b2c-scapi-custom` - inspect custom API endpoints - `b2c:b2c-custom-api-development` - build a custom API
Auf GitHub ansehen