Getting started
Layouts and titles
Most pages share a header and a menu, so you put those in a layout once.
A layout component
A layout is a normal component that wraps the page. The page comes in as children:The page goes where the layout puts its <slot />:
// frontend/layouts/AppLayout.tsx
import { Link } from 'nestjs-mvc/react'
import type { ReactNode } from 'react'
export function AppLayout({ children }: { children: ReactNode }) {
return (
<>
<nav>
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
</nav>
<main>{children}</main>
</>
)
}
<!-- frontend/layouts/AppLayout.vue -->
<script setup lang="ts">
import { Link } from 'nestjs-mvc/vue'
</script>
<template>
<nav>
<Link href="/">Home</Link>
<Link href="/users">Users</Link>
</nav>
<main>
<slot />
</main>
</template>
Attach it to a page
Tell the page which layout it uses:
// frontend/pages/Users/Index.tsx
import type { ReactNode } from 'react'
import { AppLayout } from '../../layouts/AppLayout'
export default function Index() {
return <h1>Users</h1>
}
Index.layout = (page: ReactNode) => <AppLayout>{page}</AppLayout>
<!-- frontend/pages/Users/Index.vue -->
<script setup lang="ts">
import AppLayout from '../../layouts/AppLayout.vue'
defineOptions({ layout: AppLayout })
</script>
<template>
<h1>Users</h1>
</template>
The layout now stays on screen while you move between pages. Because it isn't rebuilt on every visit, it keeps its state, so an open menu stays open and a video keeps playing.
Nested layouts
You can wrap a page in more than one layout, from the outside in:
Settings.layout = (page: ReactNode) => (
<AppLayout>
<SettingsLayout>{page}</SettingsLayout>
</AppLayout>
)
<script setup lang="ts">
import AppLayout from '../../layouts/AppLayout.vue'
import SettingsLayout from '../../layouts/SettingsLayout.vue'
defineOptions({ layout: [AppLayout, SettingsLayout] })
</script>
The page title
Use Head to set the title of the browser tab:
import { Head } from 'nestjs-mvc/react'
export default function Index() {
return (
<>
<Head title="Users" />
<h1>Users</h1>
</>
)
}
<script setup lang="ts">
import { Head } from 'nestjs-mvc/vue'
</script>
<template>
<Head title="Users" />
<h1>Users</h1>
</template>
You can put other tags in Head too:
<Head>
<title>Users</title>
<meta name="description" content="Everyone in your team" />
</Head>
<Head>
<title>Users</title>
<meta name="description" content="Everyone in your team" />
</Head>
The HTML around your app
nestjs-mvc renders a basic HTML page around your app. If you want to change it, say to add a font or a favicon, pass a template:
// src/template.ts
import type { PageObject, TemplateContext } from 'nestjs-mvc'
export function template(page: PageObject, ctx: TemplateContext): string {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/favicon.ico">
${ctx.assets()}
${ctx.head()}
</head>
<body>${ctx.body()}</body>
</html>`
}
MvcModule.forRoot({ vite: {}, template })
Make sure you keep the three calls. ctx.assets() loads your code and styles, ctx.head() adds what Head renders on the server, and ctx.body() is your app.