Skip to content
Quilium Docs
Back to site
Tutorials

Add pages and publish

Turn the onepager into a small multipage site, wire the navigation, and put it online.

Your projects are a collection and each one has its own page. What is still missing is everything around it: a page that lists the work, a menu, and the difference between "it exists" and "it is online".

Live example: Slate. Same project as the previous tutorial, continued. Every YAML block and template diff below comes from that site.

What you will have at the end

A Work page listing every project, a home page previewing three of them, an About page, a menu that builds itself from the navigation, and the site published.

1. Fill the Work page

You already created this page in the previous tutorial: it is the work page that the detail URLs hang off. It has no content yet, so /work renders an empty shell.

Open the page and add a Work block to it — the same content-type the home page uses. Give it its own eyebrow and title, for example "Selected work" and "Everything we have taken from idea to screen."

That is the whole step. The block carries the projectList query, so it reads the collection and renders the projects with no further wiring.

The Work page in the backoffice, its row1 drop-zone holding a Work block titled "Selected work" and listing the projects.

The same content-type, twice. The home page and the Work page now both render a work block, each with its own title, both reading the same collection. This is the payoff of modelling by structure: a content-type is a shape, not a place.

2. Turn the home list into a preview

Now that /work lists everything, the home page does not need to. It should show three and send people on.

The number of projects is currently a constant in the schema, which means both blocks are stuck with it. Make it an editor decision instead: add a number field, and have the query read it.

limit:
  type: number
  label: How many projects
  info: 3 on the home page as a preview, 100 on the Work page.
  default: 8
projectList:
  type: query
  label: Projects
  config:
    itemSet: project
    limit: '{{field.limit}}'
    sort:
      - _order: asc

{{field.<name>}} is the value of another field of the same block — what the editor typed next to it. Set limit to 3 on the home block and 100 on the Work block, and the same content-type, with the same template, renders two different lists.

Then give the preview a way onward. Add a label field, so the link only appears where an editor asks for it:

ctaLabel:
  type: text
  label: Link to the Work page
  info: Fill it on a preview block to link to the full list. Leave empty on the Work page itself.
{% if c.ctaLabel != blank %}
<a href="{{ 'work' | url }}">{{ c.ctaLabel }}</a>
{% endif %}

That url filter needs a named route to a fixed page — the kind with no parameter at all. Create it in the backoffice, pointing at the Work page, and leave the path empty: a fixed page has no variable part.

The Routes screen, with the projectDetail route resolving to /work/view/{{project.slug}} and the work route, its path left empty, resolving to /work.

3. Add an About page

Same move, different content. Create a page with the slug about on the default page-type, then add an About block to it and fill it in: the studio's story, and the three capacities.

Two pages built the same way is what makes the model click: you are not writing pages, you are assembling blocks on a page-type.

The page creation form, with the name About, the slug about, the Visible and Active toggles on, and the Default page-type selected.

The page tree: Home, Work with Project detail nested under it and marked hidden, and About.

4. Wire the navigation

Add both pages to the main navigation. Then make the menu read it.

The quickstart template hardcodes its menu, because a onepager only ever had one page to point at:

<a href="#work"    class="nav-link label text-xs">Work</a>
<a href="#about"   class="nav-link label text-xs">About</a>
<a href="#contact" class="nav-link label text-xs">Contact</a>

Replace it with a loop over the navigation:

{% for item in sitemap.main.children %}
<a href="{{ item.url }}"
   class="nav-link label text-xs{% if item.active %} is-active{% endif %}"
   {% if item.active %}aria-current="page"{% endif %}>{{ item.navTitle }}</a>
{% endfor %}

sitemap.main is the main navigation. Each item gives you url, navTitle, and active for the page being viewed — which is how you get a highlighted menu item and a correct aria-current without tracking the current page yourself.

From here the menu is data. Add a page to the navigation and it appears; reorder it there and the menu reorders. Nothing in the template changes again.

The sitemap is already filtered. Pages marked not visible never reach the loop, so the hidden page holding your detail URLs stays out of the menu on its own. You do not need a condition for it.

Do the same for the mobile menu if your template has one — it is the same loop.

Anchors need a page now. href="#contact" worked when every section was on the one page. From /about it points at a section that is not there. Prefix in-page anchors with the home page: /#contact. Same for the logo, which should go to / rather than #top.

5. Understand draft versus published

Everything you have built so far exists, and none of it is online. Two things are worth separating:

  • A page's state says whether the page is routable at all. An inactive page returns 404 everywhere, including on your machine.
  • Publishing creates a live revision. Until you publish, visitors see the previous version, or nothing.

And one thing that is not symmetric: your projects are already live. Pages have drafts, collection entries do not, so every project you created in the previous tutorial went public the moment you saved it. Publish and go live shows how to stage entries when you need to.

"Draft" never means "inactive". Leave pages active while you work. Deactivating a page to hide it is how you end up debugging a 404 that has nothing to do with your routes.

6. Publish

Two ways, depending on how many pages you have to put online.

One page. Open it and use the green Publish button, top right. Or right-click the page in the menu and pick Publish — same thing, without leaving the tree.

A right-click on the Work page in the menu, showing Edit, Copy, Move, Publish and Delete, with the green Publish button also visible top right.

Everything at once. The menu's own dropdown carries Publish all — which is what you want here, since you have just built two pages and changed a third.

The Menu dropdown open on Publish all.

7. Push your templates

Publishing pushes content. Your templates still live on your machine, so send them too:

quilium push

Push validates the theme, warns you if your git tree is dirty, and updates the translation dictionary. See Quilium CLI.

Until you push, the hosted site runs the old templates. That is what makes a backoffice Preview link come back broken after you add a detail template locally: the server has your content but not your Liquid. Content and templates travel separately, and both have to make the trip.

What you learned

  • A page is an instance of a page-type; blocks are what you compose on it.
  • The same content-type can render on as many pages as you like, and {{field.*}} lets each block be configured by its editor.
  • The navigation is data, not markup.
  • Content publishing and template pushing are two different actions.

Where to go next


Last updated September 7, 2026.