Page Guide
This guide explains how pages work in Zova within the Cabloy monorepo.
What a page means in Zova
A Zova page is not just a route target with a template attached.
The page model is built around a controller-oriented structure that combines:
- reactive state
- TSX-based render logic
- IOC-friendly composition
- CSS-in-JS styling
That combination is one of the clearest examples of Zova’s overall design philosophy.
Create a page
Example: create a page named counter in module demo-student.
npm run zova :create:page counter -- --module=demo-studentRoute path generation
Zova automatically derives the page path from the module and page names.
Representative example:
- module:
demo-student - page:
counter - generated page path:
/demo/student/counter
This matters because the framework already has conventions for route structure. AI should reuse those conventions rather than inventing unrelated paths.
Controller definition
Representative page controller shape:
@Controller()
class ControllerPageCounter extends BeanControllerPageBase {
protected render() {
return null;
}
}Add state
Representative reactive state pattern:
class ControllerPageCounter {
count: number = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}Add render logic
Representative TSX render pattern:
class ControllerPageCounter {
protected render() {
return (
<div>
<div>count: {this.count}</div>
<button onClick={() => this.increment()}>Increment</button>
<button onClick={() => this.decrement()}>Decrement</button>
</div>
);
}
}Add style
Zova pages can also attach style through built-in CSS-in-JS support.
For choosing among local $style, dedicated style beans, shared/global styles, and token/theme surfaces, also see CSS-in-JS Guide.
Representative pattern:
class ControllerPageCounter {
cTextCenter: string;
protected async __init__() {
this.cTextCenter = this.$style({
textAlign: 'center',
});
}
}Progressive code splitting
As page complexity grows, Zova supports progressively splitting page logic into more files instead of forcing everything to remain in one controller file forever.
A useful progression is:
- single-file page structure for early-stage pages
- three-file structure with controller, render, and style split out
- more-file structure when additional render, style, or service beans are needed
This matters because Zova encourages pages to start simple and then evolve into richer structures only when the business complexity justifies it.
Single-file to three-file
A typical first step is keeping the page controller responsible for state while moving render and style into dedicated beans.
Create the first render bean
npm run zova :refactor:firstRender page/counter -- --module=demo-studentRepresentative render bean shape:
@Render()
class RenderPageCounter extends BeanRenderBase {
public render() {
return (
<div class={this.cTextCenter}>
<div>count: {this.count}</div>
<button onClick={() => this.increment()}>Increment</button>
<button onClick={() => this.decrement()}>Decrement</button>
</div>
);
}
}Create the first style bean
npm run zova :refactor:firstStyle page/counter -- --module=demo-studentRepresentative style bean shape:
@Style()
class StylePageCounter extends BeanStyleBase {
cTextCenter: string;
protected async __init__() {
this.cTextCenter = this.$style({
textAlign: 'center',
});
}
}More-file growth
When the page continues to grow, you can keep splitting responsibilities instead of forcing one large controller or render bean to absorb everything.
Representative refactors include:
- additional render beans for larger UI regions
- additional style beans for more isolated styling concerns
- dedicated service beans when business state and behavior should move out of the page controller
Create another render bean
npm run zova :refactor:anotherRender page/counter another -- --module=demo-studentCreate another style bean
npm run zova :refactor:anotherStyle page/counter another -- --module=demo-studentCreate a service bean for state management
npm run zova :create:bean service page/counter/counter -- --module=demo-studentRepresentative service bean shape:
@Service()
class ServiceCounter extends BeanBase {
count: number = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
}These refactors are supported by Zova CLI commands rather than requiring a fully manual restructuring from scratch.
Practical implications for page implementation
When generating or editing a Zova page, preserve the page/controller mental model instead of rewriting the code into a generic Vue single-file-component pattern.
A better default is:
- use the Zova page generator
- keep state, render, and style inside the intended controller-oriented structure
- reuse existing routing and styling conventions
- verify whether the active edition changes page-level UI assumptions