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: true for 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

Previous
Dialogs