Add New Page to Liftosaur Website
SkillWeb & browsingOnce added, your AI can create new public-facing pages for the Liftosaur website, each with its own routing so visitors can reach it directly. Pages arrive fully formed on the first load and stay interactive afterward. Use it whenever you want to publish a new page people can visit.
Available today. Use it from your connected AI after setup.
No other account needed.
After adding it, describe the page you want and your AI will build it with routing and rendering handled. Then visit the page to confirm it loads and behaves as expected.
Then ask your AI: use the Add New Page to Liftosaur Website skill
What your AI can do with it
- Create new public-facing pages on the Liftosaur website
- Set up routing so visitors can reach each new page
- Deliver pages fully formed on the first load
- Keep pages interactive after the initial load
What this skill tells your AI
The instructions your AI receives, as published by astashov/liftosaur in .claude/skills/add-page/SKILL.md and read by ahel’s review.
Create a new server-rendered page at the specified route. Requirements: $ARGUMENTS
Research Phase
Before creating files, read these existing examples to understand the patterns:
- Read
src/pages/allExercises/allExercisesHtml.tsx— simple HTML wrapper example - Read
src/pages/allExercises/allExercisesContent.tsx— simple content component - Read
src/allExercises.tsx— client entry hydration - Read
lambda/allExercises.tsx— lambda render function - Check
lambda/index.tsfor the route registration pattern (search forgetAllExercisesEndpoint) - Check
webpack.config.jsfor the entry point pattern
Architecture: 4-Layer Pattern
Every public page uses 4 layers. For a page at /myroute with entry name mypage:
Layer 1: Lambda Render (lambda/myPage.tsx)
import { h } from "preact";
import { IAccount } from "../src/models/account";
import { MyPageHtml } from "../src/pages/myPage/myPageHtml";
import { renderPage } from "./render";
export function renderMyPageHtml(client: Window["fetch"], account?: IAccount): string {
return renderPage(<MyPageHtml client={client} account={account} />);
}
Layer 2: HTML Component (src/pages/myPage/myPageHtml.tsx)
Wraps content in <Page>. Key props:
css={["mypage"]}/js={["mypage"]}— must match webpack entry keymaxWidth={1200}— page max widthtitle,canonical,description,ogUrl— SEO metadataaccount— optional user account for nav stateurl="/myroute"— highlights nav item in TopNavMenudata={data}— serialized for hydration (excludeclientfrom data!)
import { h, JSX } from "preact";
import { Page } from "../../components/page";
import { IAccount } from "../../models/account";
import { MyPageContent } from "./myPageContent";
interface IProps {
client: Window["fetch"];
account?: IAccount;
}
export function MyPageHtml(props: IProps): JSX.Element {
const { client, ...data } = props;
return (
<Page
css={["mypage"]} js={["mypage"]} maxWidth={1200}
title="Page Title | Liftosaur"
canonical="https://www.liftosaur.com/myroute"
description="Page description"
ogUrl="https://www.liftosaur.com/myroute"
account={props.account} data={data} client={client} url="/myroute"
>
<MyPageContent client={client} {...data} />
</Page>
);
}
Layer 3: Content Component (src/pages/myPage/myPageContent.tsx)
The interactive Preact component. Export the props interface (needed by Layer 4).
Layer 4: Client Entry (src/myPage.tsx)
import { h } from "preact";
import { PageWrapper } from "./components/pageWrapper";
import { IMyPageContentProps, MyPageContent } from "./pages/myPage/myPageContent";
import { HydrateUtils } from "./utils/hydrate";
function main(): void {
HydrateUtils.hydratePage<IMyPageContentProps>((pageWrapperProps, data) => (
<PageWrapper {...pageWrapperProps}>
<MyPageContent {...data} client={window.fetch.bind(window)} />
</PageWrapper>
));
}
main();
Wiring Steps
1. Webpack entry (webpack.config.js)
Add to entry in mainConfig:
mypage: ["./src/myPage.tsx", "./src/index.css"],
2. Lambda route (lambda/index.ts)
Add import:
import { renderMyPageHtml } from "./myPage";
Add endpoint + handler (near similar endpoints):
const getMyPageEndpoint = Endpoint.build("/myroute");
const getMyPageHandler: RouteHandler<IPayload, APIGatewayProxyResult, typeof getMyPageEndpoint> = async ({ payload }) => {
const { di } = payload;
let account: IAccount | undefined;
const userResult = await getUserAccount(payload, { withPrograms: true });
if (userResult.success) {
account = userResult.data.account;
}
return {
statusCode: 200,
body: renderMyPageHtml(di.fetch, account),
headers: { "content-type": "text/html" },
};
};
Register in router chain (~line 2700):
.get(getMyPageEndpoint, getMyPageHandler)
3. Redirects (_redirects and _redirects_staging)
Add redirect entries so the CDN (Netlify) proxies the route to the Lambda backend:
In _redirects:
/myroute https://api3.liftosaur.com/myroute 200
In _redirects_staging:
/myroute https://api3-dev.liftosaur.com/myroute 200
Place the entry before any parameterized routes for the same prefix (e.g. /myroute before /myroute/:id).
4. TopNavMenu (src/components/topNavMenu.tsx)
If page should appear in nav, update getMenuItems().
Reusable Components
- TopNavMenu + FooterPage: Auto-included via
PageWrapper(don't setnowrapperon<Page>) - ScrollableTabs (
src/components/scrollableTabs.tsx): Tabbed content - SelectLink (
src/components/selectLink.tsx): Dropdown link with bottom sheet - ExerciseImage (
src/components/exerciseImage.tsx): Exercise illustrations - Markdown (
src/components/markdown.tsx): Renders markdown - Icons:
src/components/icons/ - builtinProgramProperties (
src/models/builtinPrograms.ts): Metadata for builtin programs - ProgramDao (
lambda/dao/programDao.ts): Fetches program data from CDN
Key Rules
- The
dataprop on<Page>is serialized into a hidden#datadiv. Keep it lightweight. Settings.build()creates default settings for public pages without user context.- Split complex content into separate component files within the page directory.
- Run
npx tsc --noEmitafter creating all files to verify no TypeScript errors.
Signals
- GitHub stars
- 704
- Forks
- 110
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
add-page- Source
- github.com/astashov/liftosaur