Skip to content

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 uses for 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

  1. theme.manifest.js declares routes, extensionPoints, and sections.
  2. The router renders layout/theme.tsx → internally it embeds Wrapper and children.
  3. Pages (from routes/*) can have a loader or use section data.
  4. Sections (from sections/*) receive data through a loader with
    ts
    { request, layout, product, lang }
  5. In components, data is accessed via @pikselis/runtime hooks, e.g. useSectionData("productsSection").

2) theme.manifest.js ​

Meta ​

js
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 ​

js
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:

js
{ 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 ​

js
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:

js
{
  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:

tsx
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 useTheme from the layout.
  • Mobile overlay – reacts to the redux value ui.mobileMenuIsOpen and useBetterMediaQuery.
  • Structure – Header → content → Footer.

layout/theme.tsx ​

The main layout with meta tags, the guest cart token, and "last seen products":

  • `` – returns the title and <link rel="stylesheet" ...> tags (CSS, fonts, AOS, etc.).
  • Guest token – regenerateCartToken() is called if there is no guestToken in either localStorage or Cookies.
  • "Last seen products" – a list of the last 20 productId values is written to Cookies (valid for 30 days).

Skeleton:

tsx
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 ​

  1. Create the file routes/xyz.tsx with a React component.
  2. If you need data – add a named export export const loader = async ({ request, params }) => { ... }.
  3. In `` add an entry to routes:
    js
    { path: "/xyz", file: "routes/xyz.tsx", hasLoader: true, loader: "loader" }
  4. (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:

ts
// @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:

tsx
import { useSectionData } from "@pikselis/runtime";
const data = useSectionData("productsSection");

Example loader (product blocks) ​

ts
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 → data structure so that the client can use useSectionData(id).

6) i18n and locales ​

  • `` – the directory containing translations.
  • `` – the list of languages (e.g., lt, en).
  • handle.getMeta uses settings?.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 ​

  1. Create sections/MySection.tsx with a default export component.
  2. (Optional) add export const loader = async (args) => { ... }.
  3. In theme.manifest.js add to the sections array:
    js
    {
      id: "mySection",
      label: "My Section",
      type: "mySection",
      component: "sections/MySection.tsx",
      slots: ["home"],
      propsSchema: { type: "object", properties: { title: { type: "string" } } }
    }
  4. In the layout configuration, add mySection with its props to the appropriate slot.
  5. In the component, get the data with useSectionData("mySection").

B) An additional route with a loader ​

js
routes: [
  { path: "/faq", file: "routes/faq.tsx", hasLoader: true, loader: "loader" }
]

In the routes/faq.tsx file:

tsx
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 id if 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 an AbortController on 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 component and (optionally) a loader.
  • Data reaches a section → via useSectionData("<sectionId>").

11) Useful imports (quick reference) ​

ts
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).

Pikselis Business documentation