An Effective Twig Base Templating Setup
Tags: craftcms, twig, frontend
Published: 2020-03-23 21:09:05

---
title: An Effective Twig Base Templating Setup
published: true
date: 2019-11-21 01:29:00 UTC
tags: craftcms,twig,frontend
canonical_url: https://nystudio107.com/blog/an-effective-twig-base-templating-setup
---

# An Effective Twig Base Templating Setup

### A good base tem­plat­ing set­up for your Craft CMS Twig tem­plates pro­vides a sta­ble, sol­id foun­da­tion on which to build your projects

Andrew Welch / [nystudio107](https://nystudio107.com)

![Solid twig templating layer](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/solid-twig-templating-layer.jpg)

[Twig](https://twig.symfony.com/) is a fan­tas­tic tem­plat­ing lan­guage that fea­tures mul­ti­ple inher­i­tance of lay­out tem­plates, and is opti­mized to be an easy to use pre­sen­ta­tion layer.

This arti­cle dis­cuss­es an effec­tive Twig base tem­plat­ing set­up that I have found to work extreme­ly well for me in my [Craft CMS](https://craftcms.com/) websites.

How­ev­er, even if you use anoth­er CMS that uses Twig like [Dru­pal](https://www.drupal.org/) or [Grav](https://getgrav.org/), or you use anoth­er tem­plat­ing lan­guage entire­ly like [Blade](https://laravel.com/docs/5.8/blade) or [Antlers](https://docs.statamic.com/antlers), the prin­ci­ples dis­cussed here still apply.

<aside>
                                Think of a tem­plat­ing lan­guage as the thin lay­er of frost­ing that cov­ers the lay­er cake that is your web­site, and makes it presentable.
                            </aside>

The key thing to note here is that Twig is a tem­plat­ing lan­guage, and as such it should not be used for com­pli­cat­ed busi­ness or inten­sive calculations.

Not that it can’t han­dle either (it can) but rather that it shouldn’t.

If you’re unclear as to why, read about why Twig was cre­at­ed to begin with in the [Tem­plat­ing Engines in PHP](http://fabien.potencier.org/templating-engines-in-php.html) article.

## It’s all about that base

Over the years, on a vast array of soft­ware projects of all shapes and sizes, I’ve seen devel­op­ers chas­ing the holy grail of code reusability.

Often times it ends up being that they spend an inor­di­nate amount of time cre­at­ing ​“the one high lev­el frame­work to rule them all”, only to be con­fused when real­i­ty butts in its ugly head.

<aside>
                                Many high lev­el frame­works are either over-engi­neered <span>&amp;</span> unwieldy or so spe­cif­ic they aren’t that reusable in the end anyway.
                            </aside>

I try to be more prac­ti­cal about which things I will actu­al­ly re-use (and some would say less ambitious).

Web­sites cre­at­ed in Craft CMS tend to be more on the bespoke side of things, oth­er­wise they might be bet­ter done in a more cook­ie-cut­ter sys­tem anyway.

So what I re-use are very fun­da­men­tal things like the build sys­tem (dis­cussed in the [An Anno­tat­ed web­pack 4 Con­fig for Fron­tend Web Devel­op­ment](https://dev.to/gaijinity/an-annotated-webpack-4-config-for-frontend-web-development-5646-temp-slug-4335831) arti­cle), and a base tem­plat­ing system.

If you’re going to build any­thing of sub­stance, it’s cru­cial that the base it’s built on is robust.

![Human tower base](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/human-tower-base.jpg)

So here’s what I want out of a base tem­plat­ing system:

1. The abil­i­ty to use it unmod­i­fied on a wide vari­ety of projects
2. One tem­plate that can be used both as a web page, and as pop­up modals via AJAX / XHR
3. Imple­ment core fea­tures for me, with­out restrict­ing me in terms of flexibility
4. Allow for cre­at­ing Google AMP pages, if the project war­rants it

Often I see devel­op­ers mak­ing tem­plates that inher­it from just one lay­out, or if they use mul­ti­ple lay­outs, it’s still a sin­gle inher­i­tance chain.

Twig allows for more than that. So let’s see how one approach to lever­age this might work.

## SEO & pop­up modals

Many of the points men­tioned in the pre­vi­ous sec­tion are large­ly self-explana­to­ry, but point 2 deserves more expla­na­tion. It’s all about tem­plates work­ing both as web pages and pop­up modals loaded in via AJAX / XHR.

I fre­quent­ly work with Jonathan Melville of [Code MDD](https://codemdd.io/) on projects, and he often does designs that have con­tent in pop­up modals.

For exam­ple, if you go to the [Sea­side Events](https://seasidefl.com/events) page you’ll see a num­ber of events list­ed, and if you click on an event, you’ll see the event details in a pop­up modal:

![Seaside farmers market popup modal](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x893_crop_center-center_100_line/seaside-farmers-market-popup-modal.png)

This is great, and gives it a nice app-ish feel, allow­ing the user to view mul­ti­ple events with­out leav­ing the orig­i­nal page.

But for SEO rea­sons, as well as for canon­i­cal page link­ing rea­sons, the same con­tent can also be found on its own unique page: [Sea­side Farmer’s Mar­ket — Sat­ur­days in Novem­ber](https://seasidefl.com/events/seaside-farmers-market-november):

![Seaside farmers market webpage](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x888_crop_center-center_100_line/seaside-farmers-market-webpage.png)

This is ide­al­ly what we want to be able to do auto­mat­i­cal­ly: have the same core **con­tent** be dis­playable both with and with­out the web page ​“chrome” around it.

And this is one of the things that the base tem­plat­ing set­up does.

## The over­all structure

Here’s an overview of what this base tem­plat­ing sys­tem looks like. It may seem involved, but we’ll break it down:

![Twig base templates diagram 2x](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x1234_crop_center-center_100_line/twig-base-templates-diagram-2x.png)

The orange round­ed rec­tan­gles rep­re­sent tem­plates that will be in your <kbd>templates/_layouts/</kbd> direc­to­ry, and may vary from project to project.

The blue rec­tan­gles rep­re­sent boil­er­plate tem­plates that will be in your <kbd>templates/_boilerplate/_layouts/</kbd> direc­to­ry, and won’t change from project to project.

<aside>
                                You do not have to use the exact Twig base tem­plat­ing set­up we use; but you may ben­e­fit from tak­ing away and using the con­cepts presented
                            </aside>

If at this point you’re some­one who learns bet­ter by real-world exam­ples, the exact base tem­plat­ing sys­tem described here is used in the MIT-licensed [dev​Mode​.fm web­site Github repo](https://github.com/nystudio107/devmode).

Feel free to check it out; it’s also used in the [nystudio107/​craft boil­er­plate set­up](https://github.com/nystudio107/craft).

Mean­while, every­one else, read on! We’re going to break down each template.

**PROJECT:** will pre­fix each tem­plate that may vary from project to project

**BOIL­ER­PLATE:** will pre­fix each tem­plate that stays the same from project to project

Here we go…

## PROJECT: global-variables.twig

Due to [Twig’s Pro­cess­ing Order & Scope](https://dev.to/gaijinity/twig-processing-order-scope-2f0k-temp-slug-7955660), if we want to have glob­al vari­ables that are always avail­able in all of our tem­plates, they need to be defined in the root tem­plate that all oth­ers <kbd>extends</kbd> from. 

Since these glob­als can vary from project to project, they are not part of the boil­er­plate, but they are required for the setup.

```

{# -- Root global variables that all templates inherit from -- #}
{# -- This allows for defining site-wide Twig variables as needed -- #}
{% spaceless %}

{# -- Prefetch & preconnect headers and links -- #}
{% set prefetchUrls = [
    alias("@assetsUrl"),
] %}
{# -- General global variables -- #}
{% set baseUrl = alias('@assetsUrl') ~ '/' %}
{% set gaTrackingId = getenv('GA_TRACKING_ID') %}

{# -- Twig output from the render; this must be in a block -- #}
{% block htmlPage %}
{% endblock %}

{% endspaceless %}

```

![Blocks global variables](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x188_crop_center-center_100_line/blocks-global-variables.png)

The <kbd>global-variables.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>htmlPage</kbd> — a block that encom­pass­es the entire ren­dered HTML page

## BOIL­ER­PLATE: base-web-layout.twig

Every web­page, whether a reg­u­lar web page or a Google AMP page inher­its from this tem­plate. The set­up may look a lit­tle weird, but it’s done this way so that child tem­plates can over­ride bits like the open­ing <kbd>&lt;html&gt;</kbd> tag if they need to:

```

{# -- Base web layout template that all web requests inherit from -- #}
{% extends "_layouts/global-variables.twig" %}

{%- block htmlPage -%}
    {% minify %}
    <!DOCTYPE html>
        {% block htmlTag %}
            <html lang="{{ craft.app.language |slice(0,2) }}">
        {% endblock htmlTag %}
        {% block headTag %}
            <head>
        {% endblock headTag %}
            {% include "_boilerplate/_partials/head-meta.twig" %}
            {# -- Page content that should be included in the <head> -- #}
            {% block headContent %}
            {% endblock headContent %}
            </head>

            {% block bodyTag %}
            <body>
            {% endblock bodyTag %}
                {# -- Page content that should be included in the <body> -- #}
                {% block bodyContent %}
                {% endblock bodyContent %}
            </body>
        </html>
    {% endminify %}
{%- endblock htmlPage -%}

```

Since this is a base tem­plate that all oth­er web pages inher­it from, if we want­ed to do full page caching using the [Craft Cache tag](https://dev.to/gaijinity/the-craft-cache-tag-in-depth-207b-temp-slug-8529389), we could wrap that around the <kbd>`{% minify %}`</kbd> tags here.

![Blocks base web layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x547_crop_center-center_100_line/blocks-base-web-layout.png)

The <kbd>base-web-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>htmlTag</kbd> — the <kbd>&lt;html&gt;</kbd> tag, which child tem­plates might need to override
- <kbd>headTag</kbd> — the <kbd>&lt;head&gt;</kbd> tag, which child tem­plates might need to override
- <kbd>headContent</kbd> — what­ev­er tags need to go into the <kbd>&lt;head&gt;</kbd>
- <kbd>bodyTag</kbd> — the <kbd>&lt;body&gt;</kbd> tag, which child tem­plates might need to override
- <kbd>bodyContent</kbd> — what­ev­er tags need to go in the <kbd>&lt;body&gt;</kbd>

In addi­tion, the [\_boilerplate/\_partials/head-meta.twig](https://github.com/nystudio107/craft/blob/craft-webpack/templates/_boilerplate/_partials/head-meta.twig) par­tial that con­tains boil­er­plate tags put into the <kbd>&lt;head&gt;</kbd> is includ­ed here as well.

## BOIL­ER­PLATE: base-ajax-layout.twig

If the request is an AJAX / XHR request, we want to return just the page’s <kbd>`{% content %}`</kbd> block, with­out any of the web page ​“chrome” around it.

This is exact­ly what this tem­plate does:

```

{# -- Base layout template that all AJAX requests inherit from -- #}
{% extends "_layouts/global-variables.twig" %}

{% block htmlPage %}
    {% minify %}
        {# -- Primary content block -- #}
        {% block content %}
            <code>No content block defined.</code>
        {% endblock content %}
    {% endminify %}
{% endblock htmlPage %}

```

![Blocks base ajax layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x228_crop_center-center_100_line/blocks-base-ajax-layout.png)

The <kbd>base-ajax-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>content</kbd> — the core con­tent that is rep­re­sent­ed on the page

## BOIL­ER­PLATE: base-html-layout.twig

This is the base HTML lay­out that all HTML requests inher­it from:

```

{# -- Base HTML layout template that all HTML requests inherit from -- #}
{% extends craft.app.request.isAjax() and not craft.app.request.getIsPreview()
    ? "_boilerplate/_layouts/base-ajax-layout.twig"
    : "_boilerplate/_layouts/base-web-layout.twig"
%}

{% block htmlTag %}
    <html class="fonts-loaded" lang="{{ craft.app.language |slice(0,2) }}" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
{% endblock htmlTag %}

{# -- Page content that should be included in the <head> -- #}
{% block headContent %}
    {# -- Any <meta> tags that should be included in the <head> #}
    {% block headMeta %}
    {% endblock headMeta %}

    {# -- Any <link> tags that should be included in the <head> #}
    {% block headLinks %}
    {% endblock headLinks %}

    {# -- Inline and polyfill JS #}
    {% include "_boilerplate/_partials/head-js.twig" %}

    {# -- Any JavaScript that should be included before </head> -- #}
    {% block headJs %}
    {% endblock headJs %}

    {# -- Inline and critical CSS #}
    <style>
        [v-cloak] {display: none !important;}
        {# -- Any CSS that should be included before </head> -- #}
        {% block headCss %}
        {% endblock headCss %}
    </style>
    {% include "_boilerplate/_partials/critical-css.twig" %}

{% endblock headContent %}

{# -- Page content that should be included in the <body> -- #}
{% block bodyContent %}
    {# -- Page content that should be included in the <body> -- #}
    {% block bodyHtml %}
    {% endblock bodyHtml %}

    {#-- Site-wide JavaScript --#}
    {{ craft.twigpack.includeSafariNomoduleFix() }}
    {{ craft.twigpack.includeJsModule("app.js", true) }}
    {{ craft.twigpack.includeJsModule("styles.js", true) }}

    {# -- Any JavaScript that should be included before </body> -- #}
    {% block bodyJs %}
    {% endblock bodyJs %}
{% endblock bodyContent %}

```

![Blocks base html layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x587_crop_center-center_100_line/blocks-base-html-layout.png)

The <kbd>base-html-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>headMeta</kbd> — Any <kbd>&lt;meta&gt;</kbd> tags that should be includ­ed in the <kbd>&lt;head&gt;</kbd>
- <kbd>headLinks</kbd> — Any <kbd>&lt;link&gt;</kbd> tags that should be includ­ed in the <kbd>&lt;head&gt;</kbd>
- <kbd>headJs</kbd> — Any JavaScript that should be includ­ed before <kbd>&lt;/head&gt;</kbd>
- <kbd>headCss</kbd> — Any CSS that should be includ­ed before <kbd>&lt;/head&gt;</kbd>
- <kbd>bodyHtml</kbd> — Page con­tent that should be includ­ed in the <kbd>&lt;body&gt;</kbd>
- <kbd>bodyJs</kbd> — Any JavaScript that should be includ­ed before <kbd>&lt;/body&gt;</kbd>

In addi­tion, the [\_boilerplate/\_partials/head-js.twig](https://github.com/nystudio107/craft/blob/craft-webpack/templates/_boilerplate/_partials/head-js.twig) & [\_boilerplate/\_partials/critical-css.twig](https://github.com/nystudio107/craft/blob/craft-webpack/templates/_boilerplate/_partials/critical-css.twig) boil­er­plate par­tials are includ­ed here as well.

## BOIL­ER­PLATE: amp-base-html-layout.twig

This is the base AMP HTML lay­out that all AMP HTML requests inher­it from:

```

{# -- Base AMP HTML layout template that AMP web requests inherit from -- #}
{% extends craft.app.request.isAjax() and not craft.app.request.getIsPreview()
    ? "_boilerplate/_layouts/base-ajax-layout.twig"
    : "_boilerplate/_layouts/base-web-layout.twig"
%}

{% do seomatic.script.container().include(false) %}
{% do craft.webperf.includeBeacon(false) %}

{% block htmlTag %}
    <html ⚡ lang="{{ craft.app.language |slice(0,2) }}" class="fonts-loaded">
{% endblock htmlTag %}

{# -- Page content that should be included in the <head> -- #}
{% block headContent %}
    {# -- Any <meta> tags that should be included in the <head> #}
    {% block headMeta %}
    {% endblock headMeta %}

    {# -- Any <link> tags that should be included in the <head> #}
    {% block headLinks %}
    {% endblock headLinks %}

    {# -- Google AMP JavaScripts #}
    {% include "_boilerplate/_partials/amp-head-js.twig" %}

    {# -- Any JavaScript that should be included before </head> -- #}
    {% block headJs %}
    {% endblock headJs %}

    {# -- Boilerplate & custom AMP CSS #}
    {% include "_boilerplate/_partials/amp-boilerplate-css.twig" %}
    <style amp-custom>
    {# -- Any CSS that should be included before </head> -- #}
    {% block headCss %}
    {% endblock headCss %}
    </style>
{% endblock headContent %}

{# -- Page content that should be included in the <body> -- #}
{% block bodyContent %}
    {# -- Page content that should be included in the <body> -- #}
    {% block bodyHtml %}
    {% endblock bodyHtml %}

    {# -- AMP Analytics --#}
    {% include "_boilerplate/_partials/amp-analytics.twig" %}

    {# -- Any JavaScript that should be included before </body> -- #}
    {% block bodyJs %}
    {% endblock bodyJs %}
{% endblock bodyContent %}

```

![Blocks amp base html layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x587_crop_center-center_100_line/blocks-amp-base-html-layout.png)

The <kbd>amp-base-html-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>headMeta</kbd> — Any <kbd>&lt;meta&gt;</kbd> tags that should be includ­ed in the <kbd>&lt;head&gt;</kbd>
- <kbd>headLinks</kbd> — Any <kbd>&lt;link&gt;</kbd> tags that should be includ­ed in the <kbd>&lt;head&gt;</kbd>
- <kbd>headJs</kbd> — Any JavaScript that should be includ­ed before <kbd>&lt;/head&gt;</kbd>
- <kbd>headCss</kbd> — Any CSS that should be includ­ed before <kbd>&lt;/head&gt;</kbd>
- <kbd>bodyHtml</kbd> — Page con­tent that should be includ­ed in the <kbd>&lt;body&gt;</kbd>
- <kbd>bodyJs</kbd> — Any JavaScript that should be includ­ed before <kbd>&lt;/body&gt;</kbd>

**N.B.:** these blocks are all pur­pose­ful­ly the same as the ones used in the <kbd>base-html-layout.twig</kbd> template.

In addi­tion, the [\_boilerplate/\_partials/amp-head-js.twig](https://github.com/nystudio107/craft/blob/craft-webpack/templates/_boilerplate/_partials/amp-head-js.twig), [\_boilerplate/\_partials/amp-boilerplate-css.twig](https://github.com/nystudio107/craft/blob/craft-webpack/templates/_boilerplate/_partials/amp-boilerplate-css.twig) & [amp-analytics.twig](https://github.com/nystudio107/craft/blob/craft-webpack/templates/_boilerplate/_partials/amp-analytics.twig) boil­er­plate par­tials are includ­ed here as well.

## PROJECT: generic-page-layout.twig

This is a gener­ic page lay­out that I’ve found suits most of the projects I build, and my oth­er tem­plates <kbd>extends</kbd> it.

For sim­i­lar pages, I can even extend this lay­out to get every­thing it offers, plus what I need for anoth­er sub­set of pages. For exam­ple, see the <kbd>generic-page-layout.twig</kbd> below.

How­ev­er, if I have oth­er pages that require rad­i­cal­ly dif­fer­ent lay­outs, I’ll just cre­ate anoth­er lay­out tem­plate that extends <kbd>_boilerplate/_layouts/base-html-layout.twig</kbd> and away we go!

```

{# -- Layout template for HTML pages -- #}
{% extends "_boilerplate/_layouts/base-html-layout.twig" %}

{# -- Any <meta> tags that should be included in the <head> #}
{% block headMeta %}
{% endblock headMeta %}

{# -- Any <link> tags that should be included in the <head> #}
{% block headLinks %}
{% endblock headLinks %}

{# -- Any CSS that should be included before </head> -- #}
{% block headCss %}
    {% include "_inline-css/site-fonts.css" %}
{% endblock headCss %}

{# -- Page body -- #}
{% block bodyHtml %}
    <div id="page-container" class="overflow-hidden leading-tight">
        <confetti></confetti>
        <div id="content-container" class="bg-repeat header-background">

            {# -- Info header, including _navbar.twig -- #}
            {% include "_partials/info-header.twig" %}

            <main>
                <div class="container mx-auto pb-8">
                    {# -- Primary content block -- #}
                    {% block content %}
                    {% endblock %}
                </div>
            </main>
        </div>

        {# -- Content that appears below the primary content block -- #}
        {% block subcontent %}
        {% endblock %}

        {# -- Info footer -- #}
        {% include "_partials/info-footer.twig" %}

        {# -- HTML Footer -- #}
        {% include "_partials/global-footer.twig" %}
    </div>
{% endblock bodyHtml %}

```

![Blocks generic page layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x348_crop_center-center_100_line/blocks-generic-page-layout.png)

The <kbd>generic-page-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>content</kbd> — Pri­ma­ry con­tent block
- <kbd>subContent</kbd> — Con­tent that appears below the pri­ma­ry con­tent block

Markup in the <kbd>subContent</kbd> block will appear on web pages, but not on pages loaded in via AJAX / XHR.

In addi­tion, it includes a few par­tials for the head­er, foot­er, etc., but you can have it do what­ev­er makes the most sense to you.

## PROJECT: error-page-layout.twig

Here we fur­ther <kbd>extends</kbd> the <kbd>generic-page-layout.twig</kbd> with anoth­er lay­out tem­plate that’s specif­i­cal­ly intend­ed for error pages.

Because we have a num­ber of dif­fer­ent error pages that dis­play dif­fer­ent con­tent, but have the same basic lay­out, this is the per­fect oppor­tu­ni­ty to con­sol­i­date them in anoth­er lay­out template.

Instead of repli­cat­ing the con­tent for each error page, we can have the error pages <kbd>extends</kbd> <kbd>error-page-layout.twig</kbd> and have very light­weight error pages.

The same idea of an inher­i­tance chain can be used in sim­i­lar situations.

```

{# -- Layout template for error pages -- #}
{% extends "_layouts/generic-page-layout.twig" %}

{% block content %}
{% endblock %}

{% block subcontent %}
    <section>
        <div class="container mx-auto py-8">
            <div class="text-center p-8 mb-8">
                <h1 class="font-mono italic font-bold text-5xl pt-4">
                    {{ entry.errorHeadline ?? 'Error' }}
                </h1>
                <p class="font-sans text-xl pt-4">
                    {{ (entry.errorText ?? 'An error has occurred.') |nl2br }}
                </p>
            </div>
        </div>
    </section>

{% endblock %}

{# -- Any JavaScript that should be included before </body> -- #}
{% block bodyJs %}
{% endblock bodyJs %}

```

![Blocks generic page layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x348_crop_center-center_100_line/blocks-generic-page-layout.png)

The <kbd>error-page-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>content</kbd> — Pri­ma­ry con­tent block
- <kbd>subContent</kbd> — Con­tent that appears below the pri­ma­ry con­tent block

Markup in the <kbd>subContent</kbd> block will appear on web pages, but not on pages loaded in via AJAX / XHR.

## PROJECT: amp-generic-page-layout.twig

This is the Google AMP gener­ic page tem­plate, which mir­rors the blocks and method­ol­o­gy from the <kbd>generic-page-layout.twig</kbd> tem­plate, but is sep­a­rat­ed out to allow for the unique tags that Google AMP requires:

```

{# -- Layout template for AMP HTML pages -- #}
{% extends "_boilerplate/_layouts/amp-base-html-layout.twig" %}

{# -- Any <meta> tags that should be included in the <head> #}
{% block headMeta %}
{% endblock headMeta %}

{# -- Any <link> tags that should be included in the <head> #}
{% block headLinks %}
{% endblock headLinks %}

{# -- Any JavaScript that should be included before </head> -- #}
{% block headJs %}
{% endblock headJs %}

{# -- Any CSS that should be included before </head> -- #}
{% block headCss %}
    {% include "_partials/amp-inline-css.css" %}
    {% include "_inline-css/site-fonts.css" %}
{% endblock %}

{# -- Page body -- #}
{% block bodyHtml %}
    {% include "_partials/amp-navbar.twig" %}
    <div id="page-container" class="overflow-hidden leading-tight">
        <div id="content-container" class="bg-repeat header-background">
            {# -- Info header, including _navbar.twig -- #}
            {% include "_partials/amp-info-header.twig" %}

            <main>
                <div class="container mx-auto pb-8">
                    {# -- Primary content block -- #}
                    {% block content %}
                    {% endblock %}
                </div>
            </main>
        </div>

        {# -- Content that appears below the primary content block -- #}
        {% block subcontent %}
        {% endblock %}

        {# -- Info footer -- #}
        {% include "_partials/amp-info-footer.twig" %}

        {# -- HTML Footer -- #}
        {% include "_partials/global-footer.twig" %}
    </div>
{% endblock bodyHtml %}

```

![Blocks generic page layout](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x348_crop_center-center_100_line/blocks-generic-page-layout.png)

The <kbd>amp-generic-page-layout.twig</kbd> tem­plate has the fol­low­ing blocks that can be over­rid­den by its children:

- <kbd>content</kbd> — Pri­ma­ry con­tent block
- <kbd>subContent</kbd> — Con­tent that appears below the pri­ma­ry con­tent block

Markup in the <kbd>subContent</kbd> block will appear on web pages, but not on pages loaded in via AJAX / XHR.

## dev​Mode​.fm page: a real world example

So how does this all look with a real world exam­ple? Why, I’m glad you asked! Let’s have a look at the [dev​Mode​.fm](https://github.com/nystudio107/devmode) home page, which extends <kbd>generic-page-layout.twig</kbd>:

```

{% extends "_layouts/generic-page-layout.twig" %}

{% set includeAudioMeta = false %}

{% block headLinks %}
    {{ parent() }}
    <link rel="amphtml" href="{{ siteUrl('/amp') }}">
{% endblock headLinks %}

{% block content %}
    {% include "_partials/_meta-schema-radio-series.twig" with {
        "showInfo": showInfo,
    } only %}
    <section>
        <div>
            {% for episode in craft.entries.section("episodes").limit(1).all() %}
                <div class="flex flex-wrap">
                    {% include "episodes/_partials/_display_episode.twig" with {
                        "episode": episode,
                        "showInfo": showInfo,
                        "includeAudioMeta": includeAudioMeta,
                        "autoPlay": false,
                    } only %}
                </div>
            {% endfor %}
        </div>
    </section>
{% endblock %}

{% block subcontent %}
    {% include "episodes/_partials/_display_recent_episodes.twig" with {
        "showInfo": showInfo,
    } only %}
{% endblock %}

{# -- Any JavaScript that should be included before </body> -- #}
{% block bodyJs %}
    {{ craft.twigpack.includeJsModule("player.js", true) }}
    {{ craft.twigpack.includeJsModule("episodes.js", true) }}
{% endblock bodyJs %}

```

You can com­pare this to the [dev​Mode​.fm ren­dered home page](https://devmode.fm/).

The <kbd>index.twig</kbd> tem­plate over­rides just four blocks:

- <kbd>headLinks</kbd> — Here we add a link to point browsers at the Google AMP ver­sion of this page
- <kbd>content</kbd> — The con­tent of this page, in this case the cur­rent episode sum­ma­ry & audio player
- <kbd>subContent</kbd> — The episodes list­ing com­po­nent, dis­played under the content
- <kbd>bodyJs</kbd> — Adds some JavaScript to han­dle the play­er & episodes list­ing to the page, cour­tesy of [Twig­pack](https://nystudio107.com/plugins/twigpack)

You can see that this makes the actu­al tem­plates that we write pret­ty clean. And if this page was ever request­ed via AJAX / XHR, it’d return _just_ the <kbd>content</kbd> block.

The Google AMP ver­sion of the home­page tem­plate is very similar:

```

{% extends "_layouts/amp-generic-page-layout.twig" %}

{% if entry is not defined %}
    {% set entry = craft.entries({
        "uri": " __home__",
    }).one() %}
{% endif %}

{% do seomatic.helper.loadMetadataForUri(entry.uri) %}
{% do seomatic.script.container().include(false) %}

{% block headCss %}
    {{ parent() }}
    {{ craft.twigpack.includeFile("@webroot/dist/criticalcss/amp_index_critical.min.css") }}
{% endblock headCss %}

{% block content %}
    <section>
        <div>
            {% for episode in craft.entries.section("episodes").limit(1).all() %}
                <div class="flex flex-wrap">
                    {% include "episodes/_partials/_amp_display_episode.twig" with {
                        "episode": episode,
                        "showInfo": showInfo,
                    } only %}
                </div>
            {% endfor %}
        </div>
    </section>
{% endblock %}

{% block subcontent %}
    {% include "episodes/_partials/_amp_display_recent_episodes.twig" with {
        "showInfo": showInfo,
    } only %}
{% endblock %}

{% block bodyJs %}
{% endblock bodyJs %}

```

It’s explic­it­ly load­ing the appro­pri­ate entry, because it won’t be auto-inject­ed for us by Craft, and then it loads the appro­pri­ate meta­da­ta for the route via <kbd>seomatic.helper.loadMetadataForUri()</kbd> and excludes all scripts via <kbd>seomatic.script.container().include(false)</kbd> because Google AMP does­n’t allow for them.

It’s also using Twig­pack to include the full CSS for the page inline (as per Google AMP spec) but oth­er than that… it’s the same as the reg­u­lar web page example.

## All about that Bass

While you cer­tain­ly could just start using my boil­er­plate, odds are good you’ll want to cus­tomize some things to suit your tastes.

That’s total­ly fine. What’s impor­tant is the struc­ture and method­ol­o­gy, not the spe­cif­ic imple­men­ta­tion details.

The point of a mod­u­lar­ized sys­tem like this is that if you want­ed to add, say, a way to out­put the same con­tent in JSON for­mat, you could. Just slap in anoth­er lay­out in the right place, and away you go.

Enjoy the oblig­a­tory ​“All About That Bass” and have an excel­lent day!

Links:

- [dev​Mode​.fm web­site Github repo](https://github.com/nystudio107/devmode)
- [nystudio107/​craft boil­er­plate setup](https://github.com/nystudio107/craft)

## Further Reading

_If you want to be notified about new articles, follow [nystudio107](https://twitter.com/nystudio107) on Twitter._

<small>Copyright ©2020 nystudio107. Designed by nystudio107</small>