React‑Router Theme Framework – Guide
This document describes the skeleton of your theme framework, built on react-router, and shows how to create routes, layouts, sections, and their loaders. Everything is illustrated with real examples from your code.
1) Project essentials and structure
Core ideas
- The theme is described in the `` file – this is where metadata, extension points, routes, sections, locales, and so on are declared.
- The main layout is
. It usesfor shared UI elements and contexts. - The standard page content wrapper is ``.
- `` – additional pages/routes (some intended for premium users).
- `` – individual composable sections with their own UI and (optionally) server loaders.
Data flow in brief
theme.manifest.jsdeclares routes, extensionPoints, and sections.- The router renders
layout/theme.tsx→ internally it embedsWrapperandchildren. - Pages (from
routes/*) can have a loader or use section data. - Sections (from
sections/*) receive data through a loader withts{ request, layout, product, lang } - In components, data is accessed via
@pikselis/runtimehooks, e.g.useSectionData("productsSection").
2) theme.manifest.js
Meta
export default {
name: "clothes",
version: "1.0.0",
engine: ">=1.0 <2",
tokens: "tokens.css",
public: "public",
config: { settingsId: "86302a6a-e93a-462d-a03d-3049028e2173" },
locales: "locales",
supportedLanguages: ["lt", "en"],
...
}Explanations
- name/version/engine – the theme's identity and compatibility window.
- tokens – path to the design tokens CSS (colors, typography, etc.).
- public – the static files directory.
- config.settingsId – an external setting (e.g., for fetching remote settings).
- locales/supportedLanguages – i18n resources and supported languages.
Extension points
extensionPoints: {
layout: "layout/theme.tsx",
header: "sections/Header.tsx",
footer: "sections/Footer.tsx",
page: "layout/Page.tsx"
},- layout – the root layout (wraps the entire application).
- header/footer – the shared top/bottom, which are passed into
Wrapper. - page – the base page "container" (padding, grid, etc.).
Routes
Declared in the routes array. Minimal entry:
{ path: "/", file: "routes/home.tsx", hasLoader: false }Additionally:
loader: "loader"– points to a named export in the file (e.g.,export const loader = ...).- You can use wildcards and dynamic segments, e.g.:
/user/orders/*,/prekiu-zenklai/:handle.
Root extensions
rootExtensions: [
{ key: "products", routes: ["^/products/[^/]+$"], file: "rootExtensions/recs.server.ts" }
]Allows attaching server-side logic to certain paths (regex), e.g. for recommendations.
Sections
Sections are reusable UI blocks with configuration:
{
id: "productInfo",
label: "Product Info",
type: "productInfo",
component: "sections/ProductInfo.tsx",
slots: ["product.main"],
propsSchema: {
type: "object",
properties: {
showVendor: { type: "boolean", default: true },
enableTabs: { type: "boolean", default: false }
},
additionalProperties: false
},
blocks: [
{ type: "price", label: "Price", component: "blocks/Price.tsx", maxRepeat: 1 },
{ type: "inventoryBadge", label: "Inventory Badge", component: "blocks/InventoryBadge.tsx", maxRepeat: 3,
propsSchema: { type: "object", properties: { lowThreshold: { type: "integer", default: 5, minimum: 0 } }, additionalProperties: false }
}
],
defaults: { props: { showVendor: true }, blocks: [{ type: "price" }] }
}Important
- id – a unique identifier (used in
useSectionData(id)). - type – the section type (used in loader/config logic).
- component – the path to the React component.
- slots – where the section can be placed (e.g.,
home,category,product,product.main). - propsSchema – a JSON schema to validate the settings (props) from the layout configuration.
- blocks – internal "sub-blocks"; they enable finer-grained composition.
- defaults – initial values used when nothing is defined in the configuration.
3) Layout and wrappers
layout/Page.tsx
A minimal wrapper for content:
export default function Page({ children }: React.PropsWithChildren) {
return <section className="section-padding py-8">{children}</section>;
}layout/Wrapper.tsx
A shared UI wrapper that passes through Header, Footer, ThemeContext, handles the mobile overlay, and so on. Key highlights:
- `` – supplied by
useThemefrom the layout. - Mobile overlay – reacts to the
reduxvalueui.mobileMenuIsOpenanduseBetterMediaQuery. - Structure –
Header→content→Footer.
layout/theme.tsx
The main layout with meta tags, the guest cart token, and "last seen products":
- `` – returns the
titleand<link rel="stylesheet" ...>tags (CSS, fonts, AOS, etc.). - Guest token –
regenerateCartToken()is called if there is noguestTokenin eitherlocalStorageorCookies. - "Last seen products" – a list of the last 20 productId values is written to
Cookies(valid for 30 days).
Skeleton:
export default function Layout({ header, footer, children, scripts, useTheme }: any) {
// useLocation, useRouteData("root")
// effects: guestToken, lastSeenProductIds
return (
<Wrapper header={header} footer={footer} useTheme={useTheme}>
{children}
</Wrapper>
);
}4) Creating routes
- Create the file
routes/xyz.tsxwith a React component. - If you need data – add a named export
export const loader = async ({ request, params }) => { ... }. - In `` add an entry to
routes:js{ path: "/xyz", file: "routes/xyz.tsx", hasLoader: true, loader: "loader" } - (Optionally) use dynamics:
:param,*.
Examples from the theme
- Cart steps:
/krepselis,/krepselis/prisijungimas,/krepselis/adresai,/krepselis/mokejimas/*,/krepselis/perziura. - User area:
/user/info,/user/orders/*,/user/addresses/*,/user/wishlist. - Content:
/blog,/blog/:blogSlug,/contacts,/search,/parduotuves.
5) Creating sections and their data
Loader signature
A section's server loader receives:
// @DOC - passed request, layout, product, lang
const sectionData = await section.loader({
request: args.request,
layout,
product: baseData?.anyPageData?.product,
lang: baseData?.lang
});Using the loader
In the component:
import { useSectionData } from "@pikselis/runtime";
const data = useSectionData("productsSection");Example loader (product blocks)
export const loader = (args) => getProductsByType(args);
const getProductsByType = async ({ layout }) => {
const productsBlockSectionData = {};
const tasks = [];
for (const section of layout.sections ?? []) {
if (!['newProducts','discountedProducts','recommendedProducts','productsSection'].includes(section?.type)) continue;
const { type, limit } = section.settings;
let url = '';
switch (type) {
case 'discounted':
url = `/products?filterType=discountedIndexed&selectMerged=1&useIndexedSearch=1&perPage=${limit}`; break;
case 'recommended':
case 'new':
default:
url = `/products?selectMerged=1&useIndexedSearch=1&orderBy=createdAt&orderWay=desc&perPage=${limit}`; break;
}
tasks.push(
getApiGoNoAuth(url).then((res) => {
if (res?.success) productsBlockSectionData[section.id] = res.products;
})
);
}
await Promise.all(tasks);
return productsBlockSectionData;
};Notes
- A single loader can serve multiple sections at once (based on
layout.sections). - It returns a
sectionId → datastructure so that the client can useuseSectionData(id).
6) i18n and locales
- `` – the directory containing translations.
- `` – the list of languages (e.g.,
lt,en). handle.getMetausessettings?.siteTitle[lang]– meta depends on the language.
7) Design tokens and public files
- `` – the CSS variables the theme's design is based on.
- `` – images, fonts, additional CSS (
common.css,styles.css,index.css), third-party CSS (e.g., AOS), Google Fonts preconnect, and so on.
8) Practical recipes
A) A new section from scratch
- Create
sections/MySection.tsxwith a default export component. - (Optional) add
export const loader = async (args) => { ... }. - In
theme.manifest.jsadd to thesectionsarray:js{ id: "mySection", label: "My Section", type: "mySection", component: "sections/MySection.tsx", slots: ["home"], propsSchema: { type: "object", properties: { title: { type: "string" } } } } - In the layout configuration, add
mySectionwith its props to the appropriate slot. - In the component, get the data with
useSectionData("mySection").
B) An additional route with a loader
routes: [
{ path: "/faq", file: "routes/faq.tsx", hasLoader: true, loader: "loader" }
]In the routes/faq.tsx file:
export const loader = async ({ request }) => {
// fetch from API, cookies, whatever
return { items: [...] };
};
export default function FAQ() {
const data = useLoaderData();
return <Page>{/* render data.items */}</Page>;
}C) User lists/details with wildcards
/user/orders/*, /user/addresses/* allow deeper paths (e.g. /user/orders/123) and using params in a separate file.
9) Best practices
- Section `` stability – don't change
idif you want to preserve the link between arranged blocks and their data. - `` – strict validation makes it safe to edit through the UI.
- Loaders – aggregate multiple sections into a single request where it makes sense (like
getProductsByType). - SSR/CSR balance – keep section loaders on the server, and keep components small, with memoization where needed.
- Cache/limits – use limits for API requests (
perPage), and preferably add anAbortControlleron the client. - Separate UI from data – components should read through
useSectionData(id)and not be aware of backend URLs.
10) Quick reference
- The manifest is the single source of truth about routes, sections, and extension points.
- The layout (
theme.tsx) provides meta, the guest cart, and last-seen products. - The Wrapper ties together the Header, Footer, context, and the main content area.
- Sections are declared in the manifest + have a
componentand (optionally) aloader. - Data reaches a section → via
useSectionData("<sectionId>").
11) Useful imports (quick reference)
import { useRouteData, useSectionData, useTheme } from "@pikselis/runtime";
import { useRouteLoaderData } from "react-router";
import Cookies from "js-cookie";
import { useSelector } from "react-redux";Questions?
If needed, we can add a section on configuring the theme through settings (using settingsId) or provide full section configuration JSON examples for specific slots (e.g., home, category, product).