Bespoke Frontend
Build fully custom React applications inside Solid using module-owned custom layouts, manifest-based route registration, and Solid HTTP helpers.
Overview
Solid supports building completely bespoke user interfaces in React, outside metadata-driven form/list rendering.
This pattern is intended for app-like experiences (for example merchant apps, kiosk flows, domain-specific workflows) where you want full control over:
- Layout
- Navigation
- Components
- Styling
- API orchestration
Required Pattern
For bespoke UIs, follow this operating model:
- Create a dedicated layout component and wrapper with its own CSS.
- Keep bespoke route-level UI under
src/<module-name>/custom-layout/<layout-reference>/.... - Register bespoke routes in the owning module manifest (
<module-name>.ui-module.ts). - Build pages as pure React and use Solid HTTP helpers for backend calls.
Where Files Should Live
Bespoke UI should live inside the owning module under custom-layout/, not inside admin-layout/ extension folders.
Recommended structure:
solid-ui/src/
├── <module-name>/
│ ├── custom-layout/
│ │ └── <layout-reference>/
│ │ ├── <LayoutReference>Layout.tsx
│ │ ├── <LayoutReference>LayoutWrapper.tsx
│ │ ├── <LayoutReference>.css
│ │ ├── common/
│ │ ├── home/
│ │ ├── feature-a/
│ │ └── feature-b/
│ ├── redux/
│ └── <module-name>.ui-module.ts
└── AppRoutes.tsxExample from production-style implementation:
src/tranaction/custom-layout/merchant-app/MerchantAppLayout.tsxsrc/tranaction/custom-layout/merchant-app/MerchantAppLayoutWrapper.tsxsrc/tranaction/custom-layout/merchant-app/MerchantApp.csssrc/tranaction/tranaction.ui-module.tswith/merchant-app/...routessrc/AppRoutes.tsxconsuming aggregated module routes
Step 1: Create a Dedicated Layout
Create a layout component that controls your bespoke shell (nav, header, drawer, responsive behavior, etc.) and imports its own CSS.
import "./MerchantApp.css";
export const MerchantAppLayout = ({ children }: { children: React.ReactNode }) => {
return (
<div className="merchant-app-layout">
<main className="merchant-app-main">{children}</main>
</div>
);
};Step 2: Create a Layout Wrapper with Outlet
The wrapper connects your layout shell to nested route pages:
import { Outlet } from "react-router-dom";
import { MerchantAppLayout } from "./MerchantAppLayout";
export function MerchantAppLayoutWrapper() {
return (
<MerchantAppLayout>
<Outlet />
</MerchantAppLayout>
);
}Step 3: Register Bespoke Routes in <module>.ui-module.ts
Declare your bespoke route tree in the owning UI module manifest using routes.extraRoutes.
import { createElement } from "react";
import { AuthGuard, type SolidUiModule } from "@solidxai/core-ui";
import { MerchantAppLayoutWrapper } from "./custom-layout/merchant-app/MerchantAppLayoutWrapper";
import { HomePage } from "./custom-layout/merchant-app/home-page/HomePage";
import { ReportPage } from "./custom-layout/merchant-app/report/ReportPage";
const tranactionUiModule = {
name: "tranaction",
routes: {
extraRoutes: [
{
element: createElement(AuthGuard),
children: [
{
element: createElement(MerchantAppLayoutWrapper),
children: [
{ path: "/merchant-app/home", element: createElement(HomePage) },
{ path: "/merchant-app/report", element: createElement(ReportPage) },
],
},
],
},
],
},
} satisfies SolidUiModule;
export default tranactionUiModule;Then let the app aggregate all module manifests through solid-ui-modules.ts, and let AppRoutes.tsx remain thin:
import { useRoutes } from "react-router-dom";
import { getSolidRoutes } from "@solidxai/core-ui";
import { solidUiModuleRuntime } from "./solid-ui-modules";
export function AppRoutes() {
const routes = getSolidRoutes(solidUiModuleRuntime.routes);
return useRoutes(routes);
}Use a stable base route namespace such as:
/merchant-app/.../field-force/.../partner-portal/...
The folder name under custom-layout/ should still be a logical layout reference (for example merchant-app, public-site, partner-portal), but the mounted route path does not have to match that folder name.
You can mount a custom layout anywhere you want, including:
/merchant-app/.../partner/.../dashboard/.../
Mounting a custom layout at / makes it the effective homepage shell of the consuming Solid project. This is especially useful when using Solid as a full-stack framework for CMS-driven websites, public-facing sites, landing experiences, or other projects where the bespoke frontend should become the default application surface.
Step 4: Build Pages as Pure React
Inside bespoke pages you can use any React patterns and libraries:
- Shadcn, MUI, Chakra, Primereact, Tailwind, plain CSS, CSS Modules, etc.
- React hooks/state machines/context
- Any reusable local component architecture
No metadata schema is required for these pages.
Step 5: Choose an API Integration Style
Solid supports both of these patterns for bespoke frontend work:
- Solid HTTP helpers for direct promise-based API calls
- Redux / RTK Query integration through module-owned reducers and middleware
This is a developer choice. Both are valid and supported.
Option A: Use Solid HTTP Helpers
Use solidGet, solidPost, solidPut, solidPatch, solidDelete for direct data access.
import { useEffect, useState } from "react";
import { solidGet, solidPost } from "@solidxai/core-ui";
export function HomePage() {
const [loading, setLoading] = useState(false);
const [items, setItems] = useState<any[]>([]);
useEffect(() => {
const load = async () => {
setLoading(true);
try {
const resp = await solidGet("/merchant-dashboard");
setItems(resp?.data?.data?.records || []);
} finally {
setLoading(false);
}
};
load();
}, []);
const createItem = async () => {
await solidPost("/merchant-dashboard", { name: "New item" });
};
return <div>{loading ? "Loading..." : `Items: ${items.length}`}</div>;
}Use this style when you want lightweight, local API orchestration without central store concerns.
Option B: Use Module-Owned Redux / RTK Query
If you prefer store-backed API integration, place RTK Query APIs under the owning module's redux/ folder and register them in <module-name>.ui-module.ts.
Example:
import type { SolidUiModule } from "@solidxai/core-ui";
import { reportApi } from "./redux/reportApi";
import { transactionApi } from "./redux/getTransactionApi";
const tranactionUiModule = {
name: "tranaction",
reducers: {
[reportApi.reducerPath]: reportApi.reducer,
[transactionApi.reducerPath]: transactionApi.reducer,
},
middlewares: [
reportApi.middleware,
transactionApi.middleware,
],
} satisfies SolidUiModule;The runtime aggregates these registrations and passes them to StoreProvider through solidUiModuleRuntime.
Use this style when you want:
- shared API state across multiple screens
- caching and invalidation
- generated query/mutation hooks
- a more structured app-wide API layer
For a full walkthrough, see Redux Module Integration. For direct API helpers, see Solid HTTP API.
Styling Guidance
Each bespoke layout should own its styling boundary.
Recommended:
- Keep one root CSS file per bespoke app shell (for example
MerchantApp.css) - Use app-specific class prefixes (for example
merchant-app-*) - Keep feature-level CSS close to feature page folders (
home.css,report.css, etc.) - Keep layout assets and related feature files together under the module's
custom-layout/tree
This prevents accidental style collisions with the default Solid admin UI.
Routing and Navigation Notes
- Use nested routes under one wrapper when pages share the same bespoke shell.
- Use separate wrappers if you need multiple bespoke shells.
- Keep route paths explicit and versionable (
/merchant-app/v2/...) when evolving UX.
Integration Boundaries
Use bespoke pages when you need full UI freedom.
Use metadata-driven customization in admin-layout/ when you want to extend existing generated Solid views.
You can combine both approaches in one application.
Common Mistakes
- Placing full bespoke route UIs in
admin-layout/instead ofcustom-layout/ - Skipping a layout wrapper and duplicating shell UI across each page
- Mixing bespoke CSS with generic global selectors
- Hardcoding backend URLs instead of using
solid-httphelpers
Checklist
- Create
Layout.tsx+LayoutWrapper.tsx+ dedicated CSS undersrc/<module-name>/custom-layout/<layout-reference>/. - Register nested routes in
<module-name>.ui-module.tsunderroutes.extraRoutes. - Ensure
solid-ui/src/solid-ui-modules.tsaggregates module manifests. - Keep
AppRoutes.tsxbesideApp.tsxand usegetSolidRoutes(solidUiModuleRuntime.routes). - Ensure routes are grouped under a clear base path (
/<layout-reference>/...). - Build pages as React components.
- Choose either direct Solid HTTP helpers or module-owned Redux / RTK Query integration.