Custom Template
Custom templates are React components that you can use in your theme when none of the available system pages meet your requirements. These custom pages are available at /c/your-custom-page routes (for example, johndoe.com/c/offers).
Custom pages are defined in index.jsx file under https://github.com/gofynd/Turbo/tree/main/theme/custom-templates.
Basic Setup
To create a custom template, follow these steps:
- Inside your theme/custom-templates directory, create an index.jsx file.
- Create one or more React component files (for example, offers.jsx, careers.jsx).
- Import the components in index.jsx.
- Default export a list of React Router routes.
Here's a basic example with two custom pages: career.jsx and offers.jsx.
Index File
import React from "react";
import { Route } from "react-router-dom";
import Careers from "./careers";
import Offers from "./offers";
export default [
<Route path="careers" element={<Careers />} />,
<Route path="offers" element={<Offers />} />,
];
Career File
import React from "react";
import { useFPI } from "fdk-core/utils";
function Careers() {
const fpi = useFPI();
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Careers</h1>
<p className="text-gray-700">
Join our team! Explore career opportunities.
</p>
</div>
);
}
export default Careers;
Offers File
import React from "react";
import { useFPI } from "fdk-core/utils";
function Offers() {
const fpi = useFPI();
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Special Offers</h1>
<p className="text-gray-700">
Check out our latest deals and promotions.
</p>
</div>
);
}
export default Offers;
Sectionable Custom Pages
Sectionable custom pages allow you to add Sections on the custom page. To enable this, export a section constant from your component and use the helper function parseSections to safely parse the sections meta.
For example, index.jsx file:
import React from "react";
import { Route } from "react-router-dom";
import Offers, { sections as offersSections } from "./offers";
import Careers, { sections as careersSections } from "./careers";
// Helper function to safely parse sections
const parseSections = (sectionsString) => {
if (!sectionsString) {
return [];
}
try {
return JSON.parse(sectionsString);
} catch (e) {
// console.error(`Failed to parse sections`, e);
return [];
}
};
export default [
<Route
path="offers"
element={<Offers />}
sections={parseSections(offersSections)}
/>,
<Route
path="careers"
element={<Careers />}
sections={parseSections(careersSections)}
/>,
];
Then, export the sections from your component.
For example, offers.jsx file
export const sections = JSON.stringify([
{
attributes: {
page: "c:::offers",
},
},
]);
Server-Side Data Fetching
Server-side data fetching allows you to make API calls on the server, generate HTML, and avoid waiting for API execution on the client side. This improves performance and SEO.
To implement server-side fetching, add a serverFetch static method to your component:
import React from "react";
import { useGlobalStore, useFPI } from "fdk-core/utils";
import { FETCH_NAVIGATION_QUERY } from "../queries/headerQuery";
function Careers() {
const fpi = useFPI();
// Get data from serverFetch
const navigationData = useGlobalStore(
fpi.getters.CUSTOM_VALUE
)?.navigationData;
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Careers</h1>
{navigationData && (
<div className="p-4 bg-blue-50 mb-4 rounded">
<p>
<strong>Navigation Items:</strong> {navigationData.totalItems}
</p>
</div>
)}
<p className="text-gray-700">
Join our team! Explore career opportunities.
</p>
</div>
);
}
// Server-side data fetching
Careers.serverFetch = async function ({ fpi }) {
const response = await fpi.executeGQL(FETCH_NAVIGATION_QUERY);
const navItems = response?.data?.applicationContent?.navigations?.items;
const navigationData = {
totalItems: navItems.length,
items: navItems,
};
fpi.custom.setValue("navigationData", navigationData);
};
export const sections = JSON.stringify([
{
attributes: {
page: "c:::careers",
},
},
]);
export default Careers;
Authentication Guards
You can protect custom pages by adding an authGuard function to the route's handle property. This function determines whether a user is authenticated.
import { USER_DATA_QUERY } from "../queries/userQuery";
async function isLoggedIn({ fpi, store }) {
try {
const isBoolean = typeof store?.auth?.logged_in === "boolean";
if (isBoolean) {
const loggedIn = store?.auth?.logged_in;
return loggedIn;
}
const userData = await fpi.executeGQL(USER_DATA_QUERY);
return !!(userData?.data?.user?.logged_in_user ?? false);
} catch (error) {
return false;
}
}
// In your index.jsx
<Route
path="offers"
element={<Offers />}
handle={{
authGuard: isLoggedIn,
}}
/>;
When a user who is not logged in tries to access a protected custom page, they will be redirected to the login page.

Final Output of Example Pages
Index Page
import React from "react";
import { Route } from "react-router-dom";
import Offers, { sections as offersSections } from "./offers";
import Careers, { sections as careersSections } from "./careers";
import { isLoggedIn } from "../helper/auth-guard";
// Helper function to safely parse sections
const parseSections = (sectionsString) => {
if (!sectionsString) {
return [];
}
try {
return JSON.parse(sectionsString);
} catch (e) {
// console.error(`Failed to parse sections`, e);
return [];
}
};
export default [
<Route
path="offers"
element={<Offers />}
sections={parseSections(offersSections)}
handle={{
authGuard: isLoggedIn,
}}
/>,
<Route
path="careers"
element={<Careers />}
sections={parseSections(careersSections)}
/>,
];
Careers Page
This example demonstrates a careers page that fetches navigation data on the server:
import React from "react";
import { useGlobalStore, useFPI } from "fdk-core/utils";
import { FETCH_NAVIGATION_QUERY } from "../queries/headerQuery";
function Careers() {
const fpi = useFPI();
// Get data from serverFetch
const navigationData = useGlobalStore(
fpi.getters.CUSTOM_VALUE
)?.navigationData;
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4">Careers</h1>
{navigationData && (
<div className="p-4 bg-blue-50 mb-4 rounded">
<p>
<strong>Navigation Items:</strong> {navigationData.totalItems}
</p>
</div>
)}
<p className="text-gray-700">
Join our team! Explore career opportunities.
</p>
</div>
);
}
// Server-side data fetching
Careers.serverFetch = async function ({ fpi }) {
const response = await fpi.executeGQL(FETCH_NAVIGATION_QUERY);
const navItems = response?.data?.applicationContent?.navigations?.items;
const navigationData = {
totalItems: navItems.length,
items: navItems,
};
fpi.custom.setValue("navigationData", navigationData);
};
export const sections = JSON.stringify([
{
attributes: {
page: "c:::careers",
},
},
]);
export default Careers;
Offers Page
This example demonstrates an offers page that uses the sections:
import React from "react";
import { useGlobalStore, useFPI } from "fdk-core/utils";
import { SectionRenderer } from "fdk-core/components";
function Offers() {
const fpi = useFPI();
const page = useGlobalStore(fpi.getters.PAGE) || {};
const THEME = useGlobalStore(fpi.getters.THEME);
const mode = THEME?.config?.list.find(
(f) => f.name === THEME?.config?.current
);
const globalConfig = mode?.global_config?.custom?.props;
const { sections = [] } = page || {};
return (
page?.value === "c:::offers" && (
<SectionRenderer
sections={sections}
fpi={fpi}
globalConfig={globalConfig}
/>
)
);
}
Offers.serverFetch = () => {};
export const sections = JSON.stringify([
{
attributes: {
page: "c:::offers",
},
},
]);
export default Offers;