logo
BETA

Multiple Layouts

Use different layouts for different sections of your application

Harpy.js supports multiple layouts, allowing you to create different visual structures for different parts of your application. This is perfect for having a marketing layout for your homepage and a sidebar layout for your documentation or admin dashboard.

Why Use Multiple Layouts?

Landing Pages

Full-width marketing pages with hero sections, testimonials, and call-to-action buttons.

Documentation

Sidebar navigation layout for docs, guides, and knowledge base articles.

Authentication

Centered, minimal layout for login, register, and password reset pages.

Admin Dashboard

Complex layout with sidebar, top navigation, and content area for admin panels.

1. Setting the Default Layout

In main.ts, configure your default layout that will be used for all routes unless explicitly overridden:

TypeScript
1// src/main.ts2import { NestFactory } from '@nestjs/core';3import { FastifyAdapter } from '@nestjs/platform-fastify';4import { AppModule } from './app.module';5import { setupHarpyApp } from '@harpy-js/core';6import DefaultLayout from './layouts/layout';78async function bootstrap() {9  const app = await NestFactory.create(10    AppModule,11    new FastifyAdapter(),12  );1314  // Set the default layout for the entire application15  await setupHarpyApp(app, {16    layout: DefaultLayout,  // This is the default layout17    distDir: 'dist',18    publicDir: 'public',19  });2021  await app.listen({22    port: 3000,23    host: '0.0.0.0',24  });25}2627bootstrap();

💡 Tip: The default layout is typically a simple, full-width layout used for landing pages and marketing content.

Example: Default Layout

TypeScript
1// src/layouts/layout.tsx2import { Link, type JsxLayoutProps } from '@harpy-js/core';34export default function DefaultLayout({5  children,6  meta,7  lang,8}: JsxLayoutProps & { lang: string }) {9  return (10    <html lang={lang || 'en'}>11      <head>12        <title>{meta?.title ?? 'My App'}</title>13        <meta charSet="utf-8" />14        <meta name="viewport" content="width=device-width, initial-scale=1" />15        <link rel="stylesheet" href="/styles/styles.css" />16      </head>17      <body>18        <header>19          <nav>20            <Link href="/">Home</Link>21            <Link href="/about">About</Link>22          </nav>23        </header>24        <main>{children}</main>25        <footer>26          <p>&copy; 2025 My App</p>27        </footer>28      </body>29    </html>30  );31}

2. Creating Additional Layouts

Create additional layouts in your src/layouts/ folder. Here's an example of a dashboard layout with a sidebar:

TypeScript
1// src/layouts/dashboard-layout.tsx2import { JsxLayoutProps } from '@harpy-js/core';3import Sidebar from './components/Sidebar';45export default function DashboardLayout({6  children,7  meta,8  hydrationScripts,9  sections = [],10}: JsxLayoutProps & {11  hydrationScripts?: Array<{ componentName: string; path: string }>;12  sections?: NavSection[];13}) {14  return (15    <html lang="en">16      <head>17        <title>{meta?.title ?? 'Dashboard'}</title>18        <meta charSet="utf-8" />19        <link rel="stylesheet" href="/styles/styles.css" />20      </head>21      <body>22        <div className="flex h-screen">23          {/* Sidebar navigation */}24          <Sidebar sections={sections} />25          26          {/* Main content */}27          <main className="flex-1 overflow-auto">28            {children}29          </main>30        </div>31        32        {/* Hydration scripts */}33        {hydrationScripts?.map((script) => (34          <script35            key={script.componentName}36            type="module"37            src={script.path}38            defer39          />40        ))}41      </body>42    </html>43  );44}

Example: Authentication Layout

A minimal, centered layout for authentication pages:

TypeScript
1// src/layouts/auth-layout.tsx2import { JsxLayoutProps } from '@harpy-js/core';34export default function AuthLayout({5  children,6  meta,7}: JsxLayoutProps) {8  return (9    <html lang="en">10      <head>11        <title>{meta?.title ?? 'Sign In'}</title>12        <meta charSet="utf-8" />13        <link rel="stylesheet" href="/styles/styles.css" />14      </head>15      <body className="bg-gray-100">16        <div className="min-h-screen flex items-center justify-center">17          <div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8">18            {children}19          </div>20        </div>21      </body>22    </html>23  );24}

3. Using Layouts in Controllers

Override the default layout for specific routes by passing the layout option to the @JsxRender decorator:

Using the Default Layout

TypeScript
1// src/features/home/home.controller.ts2import { Controller, Get } from '@nestjs/common';3import { JsxRender } from '@harpy-js/core';4import HomePage from './views/home-page';56@Controller()7export class HomeController {8  @Get()9  @JsxRender(HomePage, {10    // No layout specified - uses DefaultLayout from main.ts11    meta: {12      title: 'Welcome to My App',13      description: 'Home page description',14    },15  })16  async index() {17    return {18      message: 'Welcome to the home page!',19    };20  }21}

Overriding with Dashboard Layout

TypeScript
1// src/features/dashboard/dashboard.controller.ts2import { Controller, Get } from '@nestjs/common';3import { JsxRender } from '@harpy-js/core';4import DashboardPage from './views/dashboard-page';5import DashboardLayout from '../../layouts/dashboard-layout';67@Controller('dashboard')8export class DashboardController {9  @Get()10  @JsxRender(DashboardPage, {11    layout: DashboardLayout,  // Override with DashboardLayout12    meta: {13      title: 'Dashboard',14      description: 'Application dashboard',15    },16  })17  async index() {18    return {19      stats: { users: 1250, revenue: 45600 },20    };21  }22}

Passing Data to Layouts

Layouts can receive data from controllers, such as navigation sections:

TypeScript
1// src/features/docs/docs.controller.ts2import { Controller, Get, Req } from '@nestjs/common';3import type { FastifyRequest } from 'fastify';4import { JsxRender, NavigationService } from '@harpy-js/core';5import DocsPage from './views/docs-page';6import DashboardLayout from '../../layouts/dashboard-layout';78@Controller('docs')9export class DocsController {10  constructor(private readonly navigationService: NavigationService) {}1112  @Get()13  @JsxRender(DocsPage, {14    layout: DashboardLayout,15    meta: {16      title: 'Documentation',17    },18  })19  async docs(@Req() req: FastifyRequest) {20    const currentPath = req.url ?? '/';21    const sections = this.navigationService.getSectionsForRoute(currentPath);2223    return {24      sections,  // Pass navigation sections to the layout25    };26  }27}

Real-World Example: This Documentation Site

This very documentation site uses multiple layouts! Here's how it's structured:

Project Structure

TypeScript
src/├── layouts/   ├── layout.tsx              # Default layout (landing pages)   ├── dashboard-layout.tsx    # Dashboard layout (docs, admin)   ├── auth-layout.tsx         # Authentication layout (login, register)   └── components/       ├── Sidebar.tsx       └── MobileMenu.tsx├── features/   ├── home/      └── home.controller.ts  # Uses DefaultLayout   ├── docs/      └── docs.controller.ts  # Uses DashboardLayout   └── auth/       └── auth.controller.ts  # Uses AuthLayout└── main.ts                     # Configures default layout

🏠 Homepage (/)

Uses DefaultLayout

  • • Full-width hero section
  • • Feature showcase
  • • Call-to-action buttons
  • • Footer with links

📚 Docs (/docs/*)

Uses DashboardLayout

  • • Sidebar navigation
  • • Active section highlighting
  • • Mobile-responsive menu
  • • Table of contents

Layout Props Reference

All layouts receive these props from the JsxLayoutProps interface:

PropTypeDescription
childrenReact.ReactNodeThe page content to render
metaMetaOptionsSEO metadata (title, description, etc.)
hydrationScriptsArrayClient-side hydration scripts
langstringCurrent language code (for i18n)

⚠️ Note: You can extend JsxLayoutProps with custom props by using TypeScript intersection types, as shown in the DashboardLayout example above.

Best Practices

✅ Keep Layouts Focused

Each layout should serve a specific purpose. Don't create too many layouts - typically 2-4 layouts are sufficient for most applications.

💡 Share Common Components

Extract shared header, footer, and navigation components into reusable modules that can be used across different layouts.

🎨 Consistent Styling

Maintain consistent branding, colors, and typography across all layouts even if the structure differs.

📱 Mobile-First

Design your layouts to be responsive. Complex layouts like dashboards should collapse appropriately on mobile devices.

Next Steps