---
title: Pages | Qwik Router
contributors:
  - adamdbradley
  - manucorporat
  - Oyemade
  - saisrikardumpeti
  - mhevery
  - nnelgxorz
  - igorbabko
  - solamichealolawale
  - mrhoodz
  - VinuB-Dev
  - nhayhoc
  - gioboa
  - jemsco
updated_at: '2024-03-27T18:37:26Z'
created_at: '2023-03-20T23:45:13Z'
---

import { Note } from '~/components/note/note';

# Pages

Pages are created by adding a new `index.tsx` file in the `src/routes` directory. Pages export a `default` Qwik component, which will be rendered as the content of the page.

```tsx title="src/routes/some/path/index.tsx"
import { component$ } from '@qwik.dev/core';

// Notice the default export
export default component$(() => {
  return <h1>Hello World!</h1>;
});
```

<Note>The only difference between a page and an endpoint is that an endpoint only exports an `onRequest`, `onGet`, `onPost`, `onPut`, `onDelete`, `onPatch`, or `onHead` function, which will be used to handle the incoming request.</Note>


## `head` export

Every page can export a `head` property (or function) that returns a `DocumentHead` object. The `DocumentHead` object is used to resolve the `title` of the page, as well as the `meta`, `links`, `styles` and `scripts`.

This API allows you to set the title of the page, as well as the meta, open graph, twitter tags and links. This is useful for SEO and social sharing.

```tsx {8} /head/ title="src/routes/about/index.tsx"
import { component$ } from '@qwik.dev/core';
import type { DocumentHead } from '@qwik.dev/router';

export default component$(() => {
  return <h1>About page</h1>;
});

export const head: DocumentHead = {
  // This will be used to resolve the <title> of the page
  title: 'About page',
  meta: [
    {
      name: 'description',
      content: 'This is the about page',
    },
    // Open graph
    {
      property: 'og:title',
      content: 'About page',
    },
    {
      property: 'og:description',
      content: 'This is the about page',
    },
  ],
  links: [
    {
      rel: 'canonical',
      href: 'https://example.com/about',
    },
  ],
  styles: [
    {
        style: '.error { background-color: red; }',
    },
  ],
  scripts: [
    {
      type: "application/ld+json",
      script: JSON.stringify({
        "@context": "https://schema.org",
        "@type": "ItemList",
      }),
    },
  ],
};
```

The example above sets the title, as well as some [Open Graph](https://ogp.me/) meta and the [canonical link](https://developers.google.com/search/docs/crawling-indexing/canonicalization).

<Note>HTML places the `<head>` tag as the first element within `<html>` (at the very top of the HTML content). The `<head>` section is not something that your route component renders directly because it would break the HTML streaming.</Note>

Look into `useDocumentHead()` to read and consume the `DocumentHead` object from within your component; you can also use `<DocumentHeadTags />` to render the tags directly and correctly.

### Dynamic Head

You can also export a function that returns a `DocumentHead` object, allowing you to programmatically set the `<title>`, `<meta>`, `<link>`, `<style>`, and `<script>` tags.

This allows you to configure the `<head>` using data from `routeLoader$()` or `routeAction$()`.

We can use the `resolveValue` method to get the value of a `routeLoader$()` or `routeAction$()` within the `head` function.

```tsx title="src/routes/jokes/[jokeId]/index.tsx"
import { component$ } from '@qwik.dev/core';
import { routeLoader$ } from '@qwik.dev/router';
import type { DocumentHead } from '@qwik.dev/router';

export const useJoke = routeLoader$(async (requestEvent) => {
  // Fetch a joke from a public API
  const jokeId = requestEvent.params.jokeId;
  const response = await fetch(`https://api.chucknorris.io/jokes/${jokeId}`);
  const joke = await response.json();
  return joke;
});

// Now we can export a function that returns a DocumentHead object
export const head: DocumentHead = ({resolveValue, params}) => {
  const joke = resolveValue(useJoke);
  return {
    title: `Joke "${joke.title}"`,
    meta: [
      {
        name: 'description',
        content: joke.text,
      },
      {
        name: 'id',
        content: params.jokeId,
      },
    ],
  };
};
```

#### A note on ordering and merging

The `head` exports are merged in an outward-in manner. This means that values from `index.tsx` will override the layout's `head` export, and that in turn will override the root layout's `head` export.

However, for dynamic `head()` exports (functions), the ordering is reversed. This allows to always add something to the title, for example, in a layout component.

```ts
export const head: DocumentHead = ({ head }) => {
  return {
    title: `MySite - ${head.title}`,
  };
};
```

So first all plain object `head` exports are merged, and then the function `head` exports are called in reverse order.

Merging (both from objects or functions) is done by concatenating arrays, or overriding.
If two values in arrays (like `meta` or `links`) have the same `key`, the last specified one wins. This allows you to override specific meta tags.
Other than that, entries that don't have a `key`, or have a unique `key`, are always included.

### Server-injected Head

You can also pass `documentHead` to `createRenderer()` as part of the `serverData` option.

The values passed will be used as the default values for `useDocumentHead()`, before the `head` exports are resolved. So layouts and pages can override the values set here.

```tsx title="src/entry.ssr.tsx" {10}
import { createRenderer } from "@qwik.dev/router";
import Root from "./root";

export default createRenderer((opts) => {
  return {
    app: <Root />,
    options: {
    ...opts,
    serverData: {
      ...opts.serverData,
      documentHead: {
        title: "My App",
      },
    },
  }
});
```

### Nested Layouts and Head

In an advanced case, a [layout](/docs/(qwikrouter)/layout/index.mdx) may want to modify the document title of an already resolved document head. In the example below, the page component returns the title of `Foo`. The containing layout component can read the value of the page's document head and modify it. In this example, the layout component is adding `MyCompany - ` to the title, so that when rendered, the title will be `MyCompany - Foo`. Every layout in the stack has the opportunity to return a new value.

```bash
──src/
  └─routes/
    ├─index.tsx
    └─layout.tsx
```

```tsx title="src/routes/index.tsx"
export const head: DocumentHead = {
  title: `Foo`,
};
```

```tsx title="src/routes/layout.tsx"
export const head: DocumentHead = ({ head }) => {
  return {
    title: `MyCompany - ${head.title}`,
  };
};
```

### Google Structured Data

This example integrates [Google Structured Data](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data)
which helps by providing explicit clues about the meaning of a page to Google.
If you want to embed custom JavaScript code, it would be ideal to do it lazily with [Partytown](https://partytown.qwik.dev/).
Sometimes, however, you are forced to load them immediately. The following example illustrates this.

```tsx {8} /head/ title="src/routes/about/index.tsx"
import { component$ } from '@qwik.dev/core';
import type { DocumentHead } from '@qwik.dev/router';

export default component$(() => {
  return <h1>About page</h1>;
});

export const head: DocumentHead = {
  scripts: [
    {
      props: {
        type: "application/ld+json",
      },
      script: JSON.stringify({
        "@context": "https://schema.org",
        "@type": "ItemList",
      }),
    },
  ],
};
```
The example above sets some [Structured Data Markup](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data?hl=en).

<Note>You need to change `router-head` component to render `head.scripts`.</Note>

```tsx {8} /head/ title="src\components\router-head\router-head.tsx"
import { component$ } from "@qwik.dev/core";
import { useDocumentHead, useLocation } from "@qwik.dev/router";

export const RouterHead = component$(() => {
  const head = useDocumentHead();
  const loc = useLocation();

  return (
    <>
      <title>{head.title}</title>

      {/* add this  */}
      {head.scripts.map((s) => (
        <script key={s.key} {...s.props} dangerouslySetInnerHTML={s.script} />
      ))}
    </>
  );
});

```

## `routeConfig` export

The `routeConfig` export is a unified alternative to the separate `head`, `eTag`, and `cacheKey` exports. It groups all page-level configuration into a single export and follows the same resolution rules as `head` -- it can be a static object or a function.

```tsx title="src/routes/product/[id]/index.tsx"
import { component$ } from '@qwik.dev/core';
import { routeLoader$ } from '@qwik.dev/router';
import type { RouteConfig } from '@qwik.dev/router';

export const useProduct = routeLoader$(async (requestEvent) => {
  const id = requestEvent.params.id;
  const product = await fetchProduct(id);
  return product;
});

export default component$(() => {
  const product = useProduct();
  return <h1>{product.value.name}</h1>;
});

// Static routeConfig
export const routeConfig: RouteConfig = {
  head: {
    title: 'Product Page',
    meta: [{ name: 'description', content: 'View our product' }],
  },
  eTag: 'v1',
  cacheKey: true,
};
```

### Dynamic `routeConfig`

Like `head`, you can export a function that receives `DocumentHeadProps` and returns a `RouteConfigValue`:

```tsx title="src/routes/product/[id]/index.tsx"
import type { RouteConfig } from '@qwik.dev/router';

export const routeConfig: RouteConfig = ({ resolveValue }) => {
  const product = resolveValue(useProduct);
  return {
    head: {
      title: product.name,
      meta: [{ name: 'description', content: product.description }],
    },
    eTag: `product-${product.id}-${product.updatedAt}`,
    cacheKey: true,
  };
};
```

### Layout-level `routeConfig`

Layouts can also export `routeConfig`. This is useful for defining eTag or cacheKey behavior for an entire subtree of routes:

```tsx title="src/routes/blog/layout.tsx"
import type { RouteConfig } from '@qwik.dev/router';

export const routeConfig: RouteConfig = {
  head: {
    meta: [{ key: 'og:site', property: 'og:site_name', content: 'My Blog' }],
  },
  // All blog pages will use SSR caching by default
  cacheKey: true,
};
```

Pages under this layout inherit the `cacheKey` unless they override it in their own `routeConfig`.

### Resolution rules

`routeConfig` follows the same resolution rules as `head`:

- **Static objects** are merged outward-in (page values override layout values)
- **Functions** are executed inner-before-outer (page first, then layouts), receiving the accumulated state in `props.head`
- For `head`: arrays (`meta`, `links`, etc.) are merged by `key`; `title` is overwritten
- For `eTag` and `cacheKey`: the last defined value wins (page overrides layout)

When a module exports `routeConfig`, the separate `head`, `eTag`, and `cacheKey` exports on that module are ignored. Modules can be mixed -- a layout can use a standalone `head` export while a page uses `routeConfig`.

For details on `eTag` and `cacheKey`, see [SSR Caching](/docs/(qwikrouter)/caching/index.mdx#ssr-caching).

