Skip to main content

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.