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:
templates/pages/category.html
is the main entry point.category.html
compiles content for the "head" and "page" blocks.- 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".
category.html
injectstemplates/layout/base.html
, which is where the first content rendered into the actual page response begins.base.html
and all its child components (likebody.html
) are rendered. Wherever{{#block}}
is encountered, the content previously registered for the matching name is output.