ServerFetch Function
Headless Turbo fetches all of its data in the browser. theme/app.jsx boots the app entirely client-side with ReactDOM.createRoot, so there is no server-rendering step and nothing that invokes a serverFetch function. This is a deliberate difference from the hosted Fynd Commerce theme, where a page or section defines and the Theme Engine calls it on the server during Server-Side Rendering (SSR) so data is ready before HTML reaches the browser.
Fetching Data at the Component Level
In place of serverFetch, every Headless Turbo page and section fetches its own data with a plain useEffect that runs after the component mounts in the browser, calling fpi.executeGQL() directly instead of receiving fpi, router, and cookies through a special function signature. For example, the product listing page fetches its data like this:
// Simplified from theme/page-layouts/plp/useProductListing.jsx
import { useEffect } from "react";
import { useGlobalStore } from "fdk-core/utils";
const ProductListing = ({ fpi }) => {
const productLists = useGlobalStore(fpi.getters.PRODUCTS) || {};
useEffect(() => {
const searchParams = new URLSearchParams(location?.search);
fpi.executeGQL(PLP_PRODUCTS, {
pageType: "number",
first: 12,
filterQuery: searchParams?.toString(),
sortOn: searchParams?.get("sort_on"),
search: searchParams?.get("q"),
});
}, [location?.search]);
return (
<>
{/* Render UI based on store data */}
</>
);
};
// No serverFetch attached — every page/section component in Headless Turbo
// fetches its own data this way.
export default ProductListing;
Where the hosted theme's ProductListing.serverFetch runs on the server before the first paint, this useEffect runs client-side after mount. The trade-off is a later data fetch (and a loading state to design for) in exchange for not needing a server-rendering runtime at all.
Fetching Data Above the Component Level
Two more layers sit above individual components, and both are plain application code with no engine invoking them — see Global Provider Resolver for full details:
// 1. Once at bootstrap, before first render — theme/app.jsx calls this directly
await globalDataResolver?.({ fpi, applicationID });
// Owns: header, footer, application configuration, currency/locale
// 2. On every route change — theme/layouts/RootLayout.jsx calls this in a useEffect
pageDataResolver({ fpi, themeId, router: { ...currentMatch, filterQuery } });
// Owns: the CMS page mapped to the current route slug
The hosted theme has no direct equivalent to this bootstrap/route split — its serverFetch contract runs per-page during SSR instead, so a hosted page combines what Headless Turbo splits across pageDataResolver (route-level) and the component's own useEffect (component-level).
Registering a New Page
Because there's no serverFetch-style contract to attach data-fetching to, adding a page is just a routing change. Headless Turbo does not use a theme-bootstrap function that returns getHeader/getProductListing-style page getters the way the hosted theme's theme/index.jsx does; pages are registered as ordinary react-router-dom routes in theme/routes.jsx, lazy-loaded and wrapped so they receive the shared fpi instance:
// theme/routes.jsx
function page(Component) {
return (
<Suspense fallback={<PageLoader />}>
<RenderWithFPI Component={Component} />
</Suspense>
);
}
export const routes = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<RootLayout />}>
<Route index element={page(Home)} handle={{ pageType: "home" }} />
{/* additional <Route> entries for other pages */}
</Route>,
),
);
To add a new page, create the component under theme/pages/ (or theme/page-layouts/) and add a <Route> entry for it in theme/routes.jsx, then fetch its data with a useEffect as shown above — there's no separate registration step or bootstrap object to update.