Patterns
Page Pattern
Every page in @wayvo-ai/core follows a consistent two-file pattern: page.tsx (entry point) and page-content.tsx (main content).
File Structure
src/app/(secure)/module/feature/
├── page.tsx # Entry point with PageShell
├── page-content.tsx # Main content with PageLayoutTemplate
├── hooks/
│ ├── use-store.ts
│ ├── use-table-columns.tsx
│ └── smart-search-columns.ts
└── components/
└── edit-form.tsx
1. Page Entry Point (page.tsx)
Note: PageShell already includes ErrorBoundary and Suspense internally.
'use client';
import { PageShell } from '@wayvo-ai/core/ui';
import dynamic from 'next/dynamic';
const PageContent = dynamic(() => import('./page-content'), { ssr: false });
export default function PageName() {
return (
<PageShell title="Page Title" noPadding>
<PageContent />
</PageShell>
);
}
With Route Params
'use client';
import { use } from 'react';
import { PageShell } from '@wayvo-ai/core/ui';
import dynamic from 'next/dynamic';
const PageContent = dynamic(() => import('./page-content'), { ssr: false });
interface PageProps {
params: Promise<{ id: string }>;
}
export default function DetailPage({ params }: PageProps) {
const { id } = use(params);
return (
<PageShell title="Detail Page" noPadding>
<PageContent id={id} />
</PageShell>
);
}
2. Page Content (page-content.tsx)
'use client';
import { Icon } from 'lucide-react';
import { PageLayoutTemplate } from '@wayvo-ai/core/ui';
import { useStore } from '@wayvo-ai/core/client';
import type { DataType } from '@/lib/common/ds/types/module/DataType';
import useTableColumns from './hooks/use-table-columns';
import useSmartSearchColumns from './hooks/smart-search-columns';
import EditForm from './components/edit-form';
export default function PageContent() {
const store = useStore<DataType>({
page: 'page-id',
datasourceId: 'DataSourceId',
alias: 'store-alias',
limit: 20,
includeCount: true,
autoQuery: true,
});
const tableColumns = useTableColumns(store);
const smartSearchColumns = useSmartSearchColumns();
return (
<PageLayoutTemplate
title="Page Title"
subTitle="Page description"
icon={<Icon className="size-12 text-muted-foreground" />}
store={store}
smartSearchColumns={smartSearchColumns}
tableColumns={tableColumns}
pageId="page-id"
itemId="item-id"
editForm={<EditForm store={store} />}
getDefaultRow={() => ({})}
addNewButtonText="Add New Item"
/>
);
}
Key Rules
- PageShell wraps the entire page in
page.tsx - PageLayoutTemplate is used in
page-content.tsx(NOT wrapped in PageShell again) - Use
dynamic()import for page-content to enable code splitting - Store configuration should include
includeCount: truefor pagination - Separate hooks for table columns, search columns, and store
- Keep edit forms in separate component files
- Default exports are required for Next.js pages
Next Steps
- CRUD Operations - Complete CRUD patterns
- Multi-tab Detail - Detail pages with tabs