1.1.2: Pages, Layouts and Components

Pages, Layouts and Components

With the critical concepts of template nesting and blocks/partials in our toolbelt, we're ready to take our first real look at overall page composition in Stencil.

Notice that the templates folder in the theme root directory contains three subdirectories:

  • components
  • layout
  • pages

Each of these houses a particular type of template, and each of these three types serves a distinct role. Let's take each one in turn.

Layout Templates

Templates in the layout directory define the "outermost" or "top level" HTML structure that is common to multiple pages on the storefront.

You'll notice that there are only two by default: a very stripped down empty.html layout, and the base.html layout that provides the frame for most pages. A bird's eye view of base.html looks something like this:

<!DOCTYPE html>
<html class="no-js" lang="{{ locale_name }}">
    <head>
        <title>{{ head.title }}</title>
        ...
        {{#block "head"}} {{/block}}
        ... (a lot of common <head> content)
    </head>
    <body>
        ...

        {{> components/common/header }}
        {{> components/common/body }}
        {{> components/common/footer }}

        ... (a few common scripts)

        {{{footer.scripts}}}
    </body>
</html>

This outer frame, with the <head> and <body> tags and a slew of global elements, is not defined on a page-by-page basis, but only once in the layout file. The layout is then applied to each page, as we'll see next.

An important thing to note here is the block definition:

{{#block "head"}} {{/block}}

Apart from including global mark-up, the main job of a layout is to define the blocks that pages will then populate with partials. (Some, like "head", are defined directly in the layout template, while others are defined in its child components.)

While virtually all pages use the base.html layout by default, you are free to create and apply other layouts. (Be mindful to avoid duplication of identical content between different layouts, likely by moving certain HTML into components.)

Page Templates

A page template represents the initial entry point for a particular page (or page type). This is where the output of content kicks off.

Page templates rarely, if ever, output HTML directly. Let's take a careful look at a default example, in this case templates/pages/product.html:

---
product:
    videos:
        limit: {{theme_settings.productpage_videos_count}}
    ...
---
{{inject 'productId' product.id}}

{{#partial "page"}}
    ... (Lots of page content)
{{/partial}}

{{> layout/base}}

The YAML and the {{inject}} declaration at the top are both concepts we'll cover in later chapters. The key things to note are the "page" partial and the final {{> layout/base}} declaration.

All of the unique output this page template provides is contained within the "page" partial, and as we know, this content is not output directly but registered for inclusion wherever the matching block is defined. The first real output of the page, then, actually starts at the very bottom when the "base" layout is injected. Filling the "slots" defined by the layout is the main job of page templates.

The layout template that is injected determines what layout is used. Simply reference the appropriate name to use a particular layout for a particular page. (You could even use a conditional structure, which we'll look at shortly, to use different layouts based on dynamic data!)

The routing of a page request to a specific template is internal to the BigCommerce SaaS application; you don't have any direct control over how the routing of URL patterns to entry points is done. (For certain page types, however, you have the option of creating multiple custom templates - a concept we'll revisit in a later chapter.)

The file names of the numerous page templates are self-explanatory; it shouldn't take much sleuthing to work out which templates serve as the entry point for which pages on your storefront. Here are some of the most crucial:

Page Type Template
Account section pages account/*
Login and registration related pages auth/*
Error pages errors/*
Main blog page blog.html
Individual blog post blog-post.html
Brand page brand.html
Category category.html
Product product.html
Cart cart.html
Checkout* checkout.html

An Aside

* The checkout page template includes the checkout header and injects the main checkout interface, but that main interface is not part of the Stencil theme and cannot be edited there. A custom checkout "package" must be registered in your store to make customizations to the checkout interface.

Component Templates

Component templates are the most straightforward type, being simply generic, single-purpose building blocks that can be injected anywhere else (in layout templates, in page templates, or in other component templates).

In templates/components, you'll see that most templates are organized in subdirectories according to their purpose (like account, cart, category, product). The common subdirectory contains global components used in various contexts (like the site header, breadcrumbs, or generic pagination controls).

Most of the content and structure of the theme is contained within component templates rather than directly in layouts or pages.

Putting It All Together

Armed with an understanding of the different template types and their roles, let's walk through a real use case and see how a page is constructed.

We've already seen the main layout (templates/layout/base.html):

<!DOCTYPE html>
<html class="no-js" lang="{{ locale_name }}">
    <head>
        ...
        {{#block "head"}} {{/block}}
        ...
    </head>
    <body>
        ...
        {{> components/common/body }}
        ...
    </body>
</html>

We see one block defined here directly ("head"), but no others. However, the layout does inject the template components/common/body. Let's see what we find there:

<main class="body" id="main-content" ...>
    {{#block "hero"}} {{/block}}
    <div class="container">
        {{#block "page"}} {{/block}}
    </div>
    ...
</main>

This "body" component is where we find the definition of the "page" block.

Now turning our attention to templates/pages/category.html, let's look at a stripped-down representation:

...
{{#partial "head"}}
    {{#if pagination.category.previous}}
        <link rel="prev" href="{{pagination.category.previous}}">
    {{/if}}
    {{#if pagination.category.next}}
        <link rel="next" href="{{pagination.category.next}}">
    {{/if}}
{{/partial}}

{{#partial "page"}}

    {{> components/common/breadcrumbs breadcrumbs=breadcrumbs}}
    {{#if category.image}}
        {{> components/common/responsive-img
            ...
        }}
    {{/if}}
    ...
    {{{category.description}}}
    <div class="page">
        {{#if category.faceted_search_enabled}}
            <aside class="page-sidebar" ...>
                {{> components/category/sidebar}}
            </aside>
        ...
        {{/if}}

        <div class="page-content" ...>
            {{> components/category/product-listing}}
            ...
        </div>
    </div>

{{/partial}}
{{> layout/base}}

This template defines content for both the "head" and "page" partials, then directly injects the base template. The main body of the page comes from yet other component templates (such as components/category/product-listing.html).

When a category page is requested:

  1. templates/pages/category.html is the main entry point.
  2. category.html compiles content for the "head" and "page" blocks.
  3. Component templates injected in the "page" partial are processed, which likely inject other components, which may inject others, etc. The rendered result of this entire template hierarchy is the registered content for "page".
  4. category.html injects templates/layout/base.html, which is where the first content rendered into the actual page response begins.
  5. base.html and all its child components (like body.html) are rendered. Wherever {{#block}} is encountered, the content previously registered for the matching name is output.

Complete and Continue