The design needs to be easy to maintain for the future—I made the decision early-on to keep things clean and fairly minimal, and to let typography do the talking. Colour palettes are generally kept to black and white with the occasional flourish of historic Textpattern yellow where appropriate. I’ve sketched various images used throughout the site design for added interest, and basically because I can knock out a new drawing relatively quickly if needed without extra Photoshop or vector work. Hand-drawn images can also be quite heavily JPEG-compressed without compromising image quality (since they are sketchy in the first place). Result!

Build tools

CSS is compiled from Sass-format modules and I use a Grunt-based task runner to build and optimise the final assets used on the live site. Textpattern files (Form templates and Page templates) are then injected into the CMS from flat files using the out_flat plugin.

Note: The Textpattern live website is now running Textpattern >=4.7, since I need some of the lovely features only available from version 4.7 onwards such as shortcodes (more information about those will be documented in a future blog article) and JSON-LD support (via escape="json" attributes).

Higher display resolutions

Use of SVG icons and high resolution images (via the <img> tag’s srcset attribute) allow for pin-sharp display on high-DPI devices. Using the Textpattern plugin smd_thumbnail and the new <txp:evaluate> tag in Textpattern 4.7 we can generate and manipulate multiple resolution sizes of each image in the CMS. This can lead to some powerful image handling, as you can see in my ‘kitchen-sink’ images Form template below I use in this site.

Note that since I wanted to fully support structured data (using Schema.org microdata) there are a lot of additional data/attributes stored per image over and above the standard <img> tags. There is a fallback in case the author has not generated a 1x and 2x version of an image, plus the <txp:evaluate test="image_info"> Textpattern tag tests whether an image has an associated caption and if so wraps it within a <figure> HTML tag with an inner <figcaption> HTML tag.

My ‘kitchen-sink’ images Form template:

<txp:evaluate test="image_info">
  <figure itemprop="image" itemscope itemtype="https://schema.org/ImageObject">
    <txp:smd_if_thumbnail type="article-832w">
      <txp:smd_thumbnail type="article-832w">
        <img width="<txp:evaluate query='ceiling(<txp:image_info type="w" />div2)' />"
          height="<txp:evaluate query='ceiling(<txp:image_info type="h" />div2)' />"
          itemprop="url"
          alt="<txp:image_info type="alt" />"
          src="<txp:smd_thumbnail_info item="url" />"
          srcset="<txp:image_url /> 2x, <txp:smd_thumbnail_info item="url" /> 1x">
        <meta itemprop="width"
          content="<txp:evaluate query='ceiling(<txp:image_info type="w" />div2)' />">
        <meta itemprop="height"
          content="<txp:evaluate query='ceiling(<txp:image_info type="h" />div2)' />">
      </txp:smd_thumbnail>
    <txp:else />
      <img width="<txp:image_info type="w" />"
        height="<txp:image_info type="h" />"
        itemprop="url"
        alt="<txp:image_info type="alt" />"
        src="<txp:image_url />">
      <meta itemprop="width" content="<txp:image_info type="w" />">
      <meta itemprop="height" content="<txp:image_info type="h" />">
    </txp:smd_if_thumbnail>
    <figcaption itemprop="caption">
      <txp:image_info />
    </figcaption>
  </figure>
<txp:else />
  <p itemprop="image" itemscope itemtype="https://schema.org/ImageObject">
    <txp:smd_if_thumbnail type="article-832w">
      <txp:smd_thumbnail type="article-832w">
        <img width="<txp:evaluate query='ceiling(<txp:image_info type="w" />div2)' />"
          height="<txp:evaluate query='ceiling(<txp:image_info type="h" />div2)' />"
          itemprop="url"
          alt="<txp:image_info type="alt" />"
          src="<txp:smd_thumbnail_info item="url" />"
          srcset="<txp:image_url /> 2x, <txp:smd_thumbnail_info item="url" /> 1x">
        <meta itemprop="width"
          content="<txp:evaluate query='ceiling(<txp:image_info type="w" />div2)' />">
        <meta itemprop="height"
          content="<txp:evaluate query='ceiling(<txp:image_info type="h" />div2)' />">
      </txp:smd_thumbnail>
    <txp:else />
      <img width="<txp:image_info type="w" />"
        height="<txp:image_info type="h" />"
        itemprop="url"
        alt="<txp:image_info type="alt" />"
        src="<txp:image_url />">
      <meta itemprop="width"
        content="<txp:image_info type="w" />">
      <meta itemprop="height"
        content="<txp:image_info type="h" />">
    </txp:smd_if_thumbnail>
  </p>
</txp:evaluate>

For this site I’m just serving 2x and 1x images to keep things as simple as possible for authors, but there’s no reason why you couldn’t expand the above example to serve specifically optimised image sizes depending on the display viewport width instead. You could also trim the amount of code considerably if structured data is not essential in your site build (although I’d recommend keeping it). Hopefully this demonstrates how images within Textpattern can be as flexible as you wish to make them, with core tags plus the smd_thumbnail plugin.

Performance

Perhaps where Textpattern shines over some of it’s contemporaries is in the area of performance. The light footprint of our CMS combined with some good cacheing and performance-enhancing design choices allows for a ‘snappy’ site that renders quickly even on slower bandwidths. A few of the techniques used in no particular order, along with some external references where applicable:

  • Cloudflare as a CDN and for global cacheing.
  • Selective cacheing of individual resource-hungry Textpattern tags, and to throttle API calls, using the etc_cache Textpattern plugin.
  • Keep database queries low – assess whether piece content really needs to be dynamic and user-editable (CMS-based) or whether it can be safely hardcoded into the HTML directly. Be brutal!
  • Inline SVG icons into the CSS file as data URIs to reduce HTTP requests.
  • Keep your CSS out of the Textpattern database—there’s really no reason for it to be in there (and several very good reasons why it shouldn’t).
  • Minify everything, cssnano and UglifyJS are your friends here for CSS and JavaScript respectively. HTML itself can be minified using PageSpeed Module if you have access to your server config (it’s included in many hosting packages too)—failing that Cloudflare has an option to minify HTML on the fly.
  • Prefetch via dns-prefetch the domains of any third-party JavaScript libraries you link to with meta tags as close to the opening <head> section of your page code as possible. Load JavaScript as close to the bottom of your <body> section as possible.
  • Load the CSS as soon as possible – as close to the top of your <head> section as possible (but below the dns-prefetch rules). Since CSS is by default a render-blocking resource you’ll want to get it loaded ASAP.
  • Use the font-display: swap; descriptor in your CSS where web fonts are used, this will take the web font out of the critical rendering path and swap it in when it’s loaded (supporting browsers will render a fallback font from your declared font stack until the web font is ready).
  • async (or defer) JavaScript wherever you can.

Unfortunately the Google AdSense ad network API lets the side down a little due to it’s generally poor performance, but as it loads asynchronously this doesn’t negatively impact the critical rendering path overall. I’ll weigh up the pros and cons of an ad delivery network over the next couple of months and make a final decision on where to keep this on the site going forward based on performance versus financial reward.

Pingdom Tools - no ad network
Site speed report without advertising in situ (via Pingdom Website Speed Test).
Pingdom Tools - with ad network
Site speed report with AdSense advertising in situ (via Pingdom Website Speed Test).

AMP support

Although the performance work above reaps good rewards, you can go a step further; the plugin pat_if_amp adds support for Accelerated Mobile Pages (AMP) within Textpattern CMS. AMP uses a subset of the standard HTML tags so you have to craft an additional page template accordingly—pat_if_amp makes that process easy though. The plugin determines if the pages is being viewed in an AMP context (by using your chosen URL pattern) and points to a dedicated Textpattern Form template if so, for example:

<txp:pat_if_amp>
  <txp:output_form form="amp" />
<txp:else />
  ...standard page template...
</txp:pat_if_amp>

Special consideration needs to be given to media such as images or videos, which have proprietary HTML tags in AMP. Luckily there is an auto-generated variable set by pat_if_amp that helps. For example, all you need to state in your images Textpattern Form template is:

<txp:if_variable name="pat_amp" value="1">
  ...AMP HTML image tags...
<txp:else />
  ...standard HTML image tags...
</txp:if_variable>

Using the above method you can separate your HTML tags and produce code that is valid in both standard HTML and AMP HTML. Remember to verify all your AMP code through Google’s AMP Test tool. Any AMP page containing code that fails the test may not benefit from AMP search features, and in some cases won’t appear in search results at all (although Google have relaxed their stance on this recently). According to Google…

Make sure your AMP is valid so that your pages work as expected for users and can be included in AMP-related features. Pages with invalid AMP will not be eligible for some Search features.

View the AMP version of this article Update 30th July 2020: We have decided to remove AMP from our website as we feel it doesn’t align with our standard of ethics.

Source code

A design patterns document is available showing how various elements of this website are styled, and the complete source code for this website build is available on GitHub—hopefully there’s a technique or two in there that will be of benefit within your own designs. Of course, if you spot any errors in the codebase or on the live site then please raise an issue and I’ll investigate.

Hope you like our new look!