Pages

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

src/routes/some/path/index.tsx
import { component$ } from '@builder.io/qwik';
 
// Notice the default export
export default component$(() => {
  return <h1>Hello World!</h1>;
});

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

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 and styles.

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.

src/routes/about/index.tsx
import { component$ } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';
 
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',
    },
  ],
};

The example above sets the title, as well as some Open Graph meta and the canonical link.

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.

Look into useDocumentHead() to read and consume the DocumentHead object from within your component.

Dynamic head

You can also export a function that returns a DocumentHead object, allowing to programmatically set the <title>, <meta> or <link> tags.

This allows to configure the <head>, including the title, meta or links 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.

src/routes/jokes/[jokeId]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import type { DocumentHead } from '@builder.io/qwik-city';
 
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,
      },
    ],
  };
};

Nested layouts and head

An advanced case is that a layout 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. Next, 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.

โ”€โ”€src/
  โ””โ”€routes/
    โ”œโ”€index.tsx
    โ””โ”€layout.tsx
src/routes/index.tsx
export const head: DocumentHead = {
  title: `Foo`,
};
src/routes/layout.tsx
export const head: DocumentHead = ({ head }) => {
  return {
    title: `MyCompany - ${head.title}`,
  };
};

Google Structured Data

This example integrates Google Structured Data this 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. Sometimes, however, you are forced to load them immediately, as in the case we see in the example.

src/routes/about/index.tsx
import { component$ } from '@builder.io/qwik';
import type { DocumentHead } from '@builder.io/qwik-city';
 
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.

Note: You need to change router-head component to render head.scripts.

src\components\router-head\router-head.tsx
import { component$ } from "@builder.io/qwik";
import { useDocumentHead, useLocation } from "@builder.io/qwik-city";
 
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} />
      ))}
    </>
  );
});
 

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • adamdbradley
  • manucorporat
  • Oyemade
  • the-r3aper7
  • mhevery
  • nnelgxorz
  • igorbabko
  • solamichealolawale
  • mrhoodz
  • VinuB-Dev
  • nhayhoc
  • gioboa