Display Posts Shortcode – Full Content

The Display Posts Shortcode plugin lets you display a list of posts based on any criteria. By default it displays titles only. The example below makes it display the post’s title and full content.

  • Building a block with Advanced Custom Fields

    The new block-based editor in WordPress provides a great new content editing experience, and it’s even more powerful with custom blocks.

    The core blocks are built using React and JavaScript. You can build your own Gutenberg blocks with JavaScript as well. If you plan to distribute your block publicly as a plugin, I recommend building it with JavaScript to keep it lean and decrease dependencies on other libraries.

    When building a single website with many unique blocks, you’ll likely find it more effective to use a block-building plugin like Advanced Custom Fields. You can build blocks much faster by leveraging a tool you likely already use. This is especially useful if you’re a PHP developer with little JavaScript experience. ACF lets you build everything using PHP.

    What is Advanced Custom Fields

    Advanced Custom Fields is a plugin for building additional content editing fields throughout the WordPress interface. You can build custom metaboxes, site options, user profile fields, and term metadata.

    For a more detailed walkthrough of ACF and how it compares to similar plugins, see my Introduction to Custom Metaboxes. I plan to write more about ACF in the near future, so keep an eye on my Advanced Custom Fields category page.

    ACF is the first popular metabox plugin to add support for building blocks. Other plugins are working on it as well – see CMB2 and Carbon Fields articles on Gutenberg compatibility – but ACF is leading the way.

    Creating a block

    There are three key parts to building a Gutenberg block with ACF:

    1. Register the block with PHP
    2. Build the block editor with the ACF user interface
    3. Describe how the block is rendered using PHP

    I’ll walk you through each step required to build a simple “Team Member” block.

    I’m storing the block’s markup and styles inside the theme to keep things simple, but you could also place the code in a core functionality plugin.

    Register the block

    We’ll use the acf_register_block() to register our custom block. I’ve provided a summary of the parameters below, and for more information see the ACF documentation.

    <?php
    /**
    * Register Blocks
    * @see https://www.billerickson.net/building-gutenberg-block-acf/#register-block
    *
    */
    function be_register_blocks() {
    if( ! function_exists('acf_register_block') )
    return;
    acf_register_block( array(
    'name' => 'team-member',
    'title' => __( 'Team Member', 'clientname' ),
    'render_template' => 'partials/block-team-member.php',
    'category' => 'formatting',
    'icon' => 'admin-users',
    'mode' => 'preview',
    'keywords' => array( 'profile', 'user', 'author' )
    ));
    }
    add_action('acf/init', 'be_register_blocks' );
    view raw functions.php hosted with ❤ by GitHub
    Name

    This is your unique name for the block. ACF automatically namespaces it for you, so my team-member name becomes acf/team-member in the database.

    Title

    This is the title shown in the Gutenberg block editor.

    Render Template

    The template file used to render the block. The same template file will be used on the frontend and as the “Preview” view in the backend editor. This can either be a relative URL from the current theme, as shown above, or a full path to any file.

    Alternatively, you can use the render_callback parameter to specify a function name that output’s the block’s HTML.

    Category

    This determines in which section of the “Add Block” window it appears. The options provided by WP core are: common, formatting, layout, widgets, and embed.

    Icon

    Specify a dashicons icon to use, or a custom SVG icon.

    Mode

    This lets you control how the block is presented the Gutenberg block editor. If set to “Preview” it will render like the frontend and you can edit content in the sidebar.

    If set to “Edit” it appears like a metabox in the content area. The user can switch the mode by clicking the button in the top right corner, unless you specifically disable it with 'supports' => array( 'mode' => false ).

    Keywords

    Up to three additional terms to use when a user is searching for the block. The name is always indexed so you don’t need to repeat it as a keyword here.

    Build the block editor

    Once the block has been registered, you can now go to Custom Fields in the admin and create your block editor. It works just like a standard metabox built in ACF.

    Under the location rules, select “Block” is equal to “Team Member”.

    Build the block markup

    The final step is to write the markup for the block. I created a template partial in /partials/block-team-member.php , matching the render_template parameter I specified when registering the block. My template partial looks like:

    <?php
    /**
    * Team Member block
    *
    * @package ClientName
    * @author Bill Erickson
    * @since 1.0.0
    * @license GPL-2.0+
    **/
    $name = get_field( 'name' );
    $title = get_field( 'title' );
    $photo = get_field( 'photo' );
    $description = get_field( 'description' );
    echo '<div class="team-member">';
    echo '<div class="team-member--header">';
    if( !empty( $photo ) )
    echo wp_get_attachment_image( $photo['ID'], 'thumbnail', null, array( 'class' => 'team-member--avatar' ) );
    if( !empty( $name ) )
    echo '<h4>' . esc_html( $name ) . '</h4>';
    if( !empty( $title ) )
    echo '<h6 class="alt">' . esc_html( $title ) . '</h6>';
    echo '</div>';
    echo '<div class="team-member--content">' . apply_filters( 'ea_the_content', $description ) . '</div>';
    echo '</div>';

    Use the get_field() function to access the fields set in the block settings. You can then build the markup for your block based on your specific design requirements.

    Building a Table of Contents block

    In my article on Developing a Gutenberg Website I mentioned the Table of Contents block I built for a few clients. It dynamically lists all h2’s within the current article and links to each one.

    I started by forking the WP Anchor Header plugin. My changes include:

    • Limiting it to only h2’s
    • Only running on posts that have the table-of-contents block.
    • Before automatically adding an ID to each heading, check if one exists already. Clients can manually specify the heading’s anchor link in the Gutenberg editor so we should respect that.
    • My ea_table_of_contents() function accepts a $count parameter. We use this to list the first few headings in the article on the homepage

    The block doesn’t have any editable fields so I added a Message field describing how it works:

    Here is the full code for the table of contents block.

  • Building a Gutenberg website

    Over the past few months we’ve built a few websites using WordPress’ new block-based editor, code-named Gutenberg. I’d like to share how we approach these projects and provide some real-world examples. I’ll then get share some technical details you’ll need as a WordPress developer building a Gutenberg website.

    There are really two types of Gutenberg websites: simple and custom.

    Simple Gutenberg Websites

    Gutenberg is a huge step forward for simple content sites. Clients can easily build beautiful and engaging pages of content with the core blocks. The theme development process for these sites is mostly CSS. We ensure the core blocks look great, match the client’s brand, and can address all their content requirements.

    This is perfect for non-profits who need a low-cost, high-impact website. We used this approach for CARAS and College Discount Cards.

    We start by designing a style guide featuring all the core blocks. Our designer then works with the client to mock up the key pages using these blocks. Once the designs are approved, I style the blocks and build the pages like legos.

    Custom Gutenberg Websites

    These sites follow our traditional web development process. Rather than letting the technology constrain our design and functionality, we first examine the client’s specific needs and create discovery documentation outlining the user experience. We then design the user interface, mockup all the blocks (both core and custom), and mockup the page layouts necessary to build the site.

    In addition to styling the core blocks, we build custom blocks to implement features not currently in core. The above example of Free Together includes a “Table of Contents” block that dynamically lists and links to each heading in the article.

    On RCP (soon to launch) we built many custom blocks including a Feature Box, Icon Links, and Downloads.

    Is Gutenberg the right choice?

    Gutenberg is still beta software. There are many small bugs, and plugin updates can bring breaking changes. I’ve had to update a site three times in the past two months because Gutenberg updates to the Columns block broke content already built with columns.

    You have to consider the value proposition for each project. We currently lean towards using the classic editor unless there’s a compelling reason to use Gutenberg. Those compelling reasons often include:

    • Building a simple Gutenberg site will save us from building a more complex website that relies heavily on custom metaboxes. We can deliver a better product faster and cheaper.
    • The website requires complex content elements that are easy to implement as custom blocks but difficult to do with TinyMCE. For instance, the RCP project required 9 custom blocks. Before Gutenberg, these would be a mix of shortcodes with Shortcode UI, custom TinyMCE plugins, and metaboxes.
    • Our team will be entering most of the site content, so we’re comfortable working around Gutenberg bugs.
    • The site is unlikely to be redesigned for a long time, so we’d rather future-proof it by using the upcoming block editor.

    Here’s the “Feature Box” block we built on RCP. This is a great example of how Gutenberg can make content creation easier. Something like this would be difficult and complicated to insert in TinyMCE. See my article on Building Gutenberg blocks with ACF for more examples.

    Frontend / Backend Preview

    Backend Edit

    Gutenberg is not a page builder

    Gutenberg is a block-based content editor, not a page builder. It’s hard to describe, but it will become more clear as you use it in the real world.

    While the columns block does allow you to nest blocks inside of blocks, it’s fairly primitive and difficult to set up. The editing experience can be painful and you’ll need custom CSS to make it mobile responsive. It works for simple sites, but is not a replacement for true page builders like Beaver Builder or flexible content areas.

    We use Gutenberg for most content pages on the site; it replaces a lot of custom page templates we would have developed previously. But for certain pages like the homepage and landing pages, we’ll often disable Gutenberg for those templates and build a true page builder with Carbon Fields or Advanced Custom Fields.

    Building a Gutenberg theme

    For the most part, a Gutenberg-optimized theme is just like any other WordPress theme. Your current WordPress theme will likely work great with Gutenberg.

    Add Theme Support

    You should consider adding theme support for the new Gutenberg features. See my base child theme as an example. I’ve placed this code in functions.php.

    Wide/Full Alignment

    add_theme_support( 'align-wide' );

    This adds “wide” and “full” options to the left, right, and center alignment options. This will be used primarily for images, but other blocks also support these alignment options.

    You’ll need to add CSS to your theme for these alignment options to actually work, in the same way you have to style .alignleft for left-aligned images to work. Here’s the CSS I’m using:

    @include media(">=tablet") {
    &.alignwide,
    &.alignfull {
    margin-top: calc( 2 * #{$block-margin});
    margin-bottom: calc( 2 * #{$block-margin});
    max-width: 1000%;
    img {
    display: block;
    margin: 0 auto;
    }
    }
    &.alignfull {
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    width: auto;
    }
    &.alignwide {
    margin-left: calc( -4 * #{$block-margin});
    margin-right: calc( -4 * #{$block-margin});
    width: calc( 100% + 8 * #{$block-margin});
    }
    }
    view raw _blocks.scss hosted with ❤ by GitHub

    See the _blocks.scss file in my starter theme for more context.

    Editor Font Sizes

    <?php
    add_theme_support( 'editor-font-sizes', array(
    array(
    'name' => __( 'small', 'ea_genesis_child' ),
    'shortName' => __( 'S', 'ea_genesis_child' ),
    'size' => 12,
    'slug' => 'small'
    ),
    array(
    'name' => __( 'regular', 'ea_genesis_child' ),
    'shortName' => __( 'M', 'ea_genesis_child' ),
    'size' => 16,
    'slug' => 'regular'
    ),
    array(
    'name' => __( 'large', 'ea_genesis_child' ),
    'shortName' => __( 'L', 'ea_genesis_child' ),
    'size' => 20,
    'slug' => 'large'
    ),
    /*
    array(
    'name' => __( 'larger', 'ea_genesis_child' ),
    'shortName' => __( 'XL', 'ea_genesis_child' ),
    'size' => 24,
    'slug' => 'larger'
    )
    */
    ) );
    view raw functions.php hosted with ❤ by GitHub

    When editing paragraph text, a user can select different sizes in the settings panel on the right. This lets you define what sizes should be available. I’ve commented out the extra large size since we only use small, medium, and large.

    You’ll also have to add CSS to your theme for this to work. Rather than hardcoding the font size on the paragraphs, Gutenberg adds a CSS class like .has-small-font-size. This helps keep the content separate from the styling and will simplify redesigns in the future.

    Theme Colors

    <?php
    // -- Disable Custom Colors
    add_theme_support( 'disable-custom-colors' );
    // -- Editor Color Palette
    add_theme_support( 'editor-color-palette', array(
    array(
    'name' => __( 'Blue', 'ea_genesis_child' ),
    'slug' => 'blue',
    'color' => '#59BACC',
    ),
    array(
    'name' => __( 'Green', 'ea_genesis_child' ),
    'slug' => 'green',
    'color' => '#58AD69',
    ),
    array(
    'name' => __( 'Orange', 'ea_genesis_child' ),
    'slug' => 'orange',
    'color' => '#FFBC49',
    ),
    array(
    'name' => __( 'Red', 'ea_genesis_child' ),
    'slug' => 'red',
    'color' => '#E2574C',
    ),
    ) );
    view raw functions.php hosted with ❤ by GitHub

    Gutenberg includes a color picker in certain blocks like paragraph and button. Rather than letting clients pick any color they want, we disable the color picker and define a few brand colors.

    Like all the other theme options, these require additional CSS to function properly. When a block has the “Background Color” set to one of your theme colors, it adds a class of .has-{color}-background-color. Likewise, when the “Text Color” is set, it adds a class of .has-{color}-color.

    You can use more generic terms for the colors like primary, secondary, and tertiary. When redesigning the theme in the future, you can change the actual colors used but maintain the naming convention for a seamless transition.

    If you use the actual color names, a future redesign will either:

    • Lose styling on those elements that use colors no longer used by the theme
    • Lead to awkward backwards-compatibility styles like .has-red-color { color: $green; }
    • Require a find/replace in the database to convert old CSS classes to new ones

    Use SASS

    I can’t imagine building a Gutenberg theme without SASS. You need to generate a frontend stylesheet, classic editor stylesheet, and a Gutenberg stylesheet, all with differing amounts of CSS, and the Gutenberg parts need to be prefixed. Look at the scss directory of my base theme to see how I’m structuring it.

    For the Theme Colors feature described above, we use SASS loops to build the styles.I define all our colors and other variables in _base.scss, then loop through them in my _style-guide.scss:

    // Brand Colors
    // -- normal / darker / lighter
    $blue_1: #59BACC;
    $blue_2: #39A5B9;
    $blue_3: #80CAD8;
    $green_1: #58AD69;
    $green_2: #458D53;
    $green_3: #7ABE88;
    $orange_1: #FFBC49;
    $orange_2: #FFA916;
    $orange_3: #FFCF7C;
    $red_1: #E2574C;
    $red_2: #D83023;
    $red_3: #E98078;
    // Gutenberg color options
    // -- see editor-color-palette in functions.php
    $colors: ( blue, $blue_1 ),
    ( green, $green_1 ),
    ( orange, $orange_1 ),
    ( red, $red_1 );
    view raw _base.scss hosted with ❤ by GitHub
    /* Color Options
    --------------------------------------------- */
    @each $name, $color in $colors {
    .has-#{$name}-color {
    color: $color;
    }
    .has-#{$name}-background-color {
    background-color: $color;
    }
    }
    view raw _style-guide.scss hosted with ❤ by GitHub

    Set block width in Gutenberg

    Gutenberg already has some base styling applied to blocks, but their max-width doesn’t match the max-width we are using on the site. We use the following in gutenberg.scss to more closely mirror the frontend:

    body.gutenberg-editor-page.wp-admin {
    /* Main column width */
    .editor-post-title__block,
    .editor-default-block-appender,
    .editor-block-list__block {
    max-width: $grid-max-width;
    }
    /* Width of "wide" blocks */
    .editor-block-list__block[data-align="wide"] {
    margin-left: calc(1 * (100vw / 12));
    margin-right: calc(1 * (100vw / 12));
    max-width: calc(10 * (100vw / 12));
    }
    /* Width of "full-wide" blocks */
    .editor-block-list__block[data-align="full"] {
    max-width: none;
    }
    }
    view raw gutenberg.scss hosted with ❤ by GitHub

    Building Custom Blocks

    The biggest question most theme developers will have is “Should I build my custom blocks from scratch in JavaScript, or use a plugin like Advanced Custom Fields?”

    I think metaboxes are a great analogy for this. When I’m building a custom plugin that will be distributed and used by many, like Genesis Title Toggle, I build my metabox from scratch. This removes a plugin dependency (ACF) or a bundled library (CMB2) and keeps the plugin lean.

    When I’m building a bunch of metaboxes for a specific client’s site, it’s better to leverage an existing tool like Advanced Custom Fields, Carbon Fields, or CMB2. I’m able to provide a higher quality result much faster than if I were to build everything from scratch.

    I use the same approach to Gutenberg blocks. For client sites, I’m building my custom blocks with Advanced Custom Fields. When I release a public plugin that includes a block, it will be built from scratch.

  • Using Template Parts with Genesis

    What makes Genesis so powerful are the actions and filters that allow you to customize every aspect of the framework.

    Some of the actions are related to the Genesis loop. These control how posts are displayed, both on archive pages and as singular content. Sridhar has a nice introduction to the Genesis loop actions.

    You can use template tags to determine what content is shown based on the page context. For instance, here’s the code in Genesis that displays the featured image on post archives.

    On line 15, you’ll notice it only displays the featured image if ! is_singular(). This prevents the featured image from appearing at the top of your single posts.

    <?php
    add_action( 'genesis_entry_content', 'genesis_do_post_image', 8 );
    add_action( 'genesis_post_content', 'genesis_do_post_image' );
    /**
    * Echo the post image on archive pages.
    *
    * If this an archive page and the option is set to show thumbnail, then it gets the image size as per the theme
    * setting, wraps it in the post permalink and echoes it.
    *
    * @since 1.1.0
    */
    function genesis_do_post_image() {
    if ( ! is_singular() && genesis_get_option( 'content_archive_thumbnail' ) ) {
    $img = genesis_get_image( array(
    'format' => 'html',
    'size' => genesis_get_option( 'image_size' ),
    'context' => 'archive',
    'attr' => genesis_parse_attr( 'entry-image', array(
    'alt' => get_the_title(),
    ) ),
    ) );
    if ( ! empty( $img ) ) {
    genesis_markup( array(
    'open' => '<a %s>',
    'close' => '</a>',
    'content' => wp_make_content_images_responsive( $img ),
    'context' => 'entry-image-link',
    ) );
    }
    }
    }
    view raw post.php hosted with ❤ by GitHub

    This works for simpler sites, but as websites become more modular it presents an issue. I want to design a “post summary” module that’s used on archive pages and for related posts on the single post. Template tags aren’t sufficient because they relate to the current page type, not the specific context of the module.

    What are template parts

    Template parts – also referred to as template partials – are small files that hold reusable sections of code in a theme.

    Here’s an example of my archive partial, used most often on archive pages to display a post summary:

    <?php
    /**
    * Archive partial
    *
    * @package EAGenesisChild
    * @author Bill Erickson
    * @since 1.0.0
    * @license GPL-2.0+
    **/
    echo '<article class="post-summary">';
    echo '<a class="entry-image-link" href="' . get_permalink() . '" tabindex="-1" aria-hidden="true">' . get_the_post_thumbnail( get_the_ID(), 'medium' ) . '</a>';
    echo '<header class="entry-header">';
    echo '<h2 class="entry-title"><a href="' . get_permalink() . '">' . get_the_title() . '</a></h2>';
    echo '</header>';
    echo '<div class="entry-content">';
    the_excerpt();
    echo '<p><a class="read-more" href="' . get_permalink() . '" tabindex="-1" aria-hidden="true">Read More<span class="screen-reader-text"> of ' . get_the_title() . '</span></a></p>';
    echo '</div>';
    echo '</article>';
    view raw archive.php hosted with ❤ by GitHub

    If I want to list related posts, I loop through my custom query using the archive partial.

    <?php
    /**
    * Related Posts
    * @see https://www.billerickson.net/related-posts-with-searchwp/
    *
    */
    function be_related_posts() {
    $loop = new WP_Query( array(
    'post__in' => ea_get_related_posts( 3 ),
    'orderby' => 'post__in',
    ) );
    if( $loop->have_posts() ):
    while( $loop->have_posts() ): $loop->the_post();
    get_template_part( 'partials/archive' );
    endwhile;
    endif;
    wp_reset_postdata();
    }
    add_action( 'genesis_after_entry', 'be_related_posts' );
    view raw single.php hosted with ❤ by GitHub

    Using on archive pages

    In my recent Genesis child themes, we’ve replaced the default Genesis loop on archive pages with one that uses template parts. This gives us more freedom when leveraging the Genesis hooks elsewhere.

    With this change, the Genesis loop hooks only run on the main post in the singular context. I know that genesis_entry_header will only run on single posts and pages, and won’t run on the related posts listed after the article.

    To replace the Genesis loop on archive pages, I use the following code:

    <?php
    /**
    * Loop
    *
    * @package EAGenesisChild
    * @author Bill Erickson
    * @since 1.0.0
    * @license GPL-2.0+
    **/
    /**
    * Use Archive Loop
    *
    */
    function ea_use_archive_loop() {
    if( ! is_singular() ) {
    add_action( 'genesis_loop', 'ea_archive_loop' );
    remove_action( 'genesis_loop', 'genesis_do_loop' );
    }
    }
    add_action( 'genesis_setup', 'ea_use_archive_loop', 20 );
    /**
    * Archive Loop
    * Uses template partials
    */
    function ea_archive_loop() {
    if ( have_posts() ) {
    do_action( 'genesis_before_while' );
    while ( have_posts() ) {
    the_post();
    do_action( 'genesis_before_entry' );
    // Template part
    $partial = apply_filters( 'ea_loop_partial', 'archive' );
    $context = apply_filters( 'ea_loop_partial_context', is_search() ? 'search' : get_post_type() );
    get_template_part( 'partials/' . $partial, $context );
    do_action( 'genesis_after_entry' );
    }
    do_action( 'genesis_after_endwhile' );
    } else {
    do_action( 'genesis_loop_else' );
    }
    }
    view raw loop.php hosted with ❤ by GitHub

    You could place this directly in functions.php, or put it in a separate file and include that in functions.php.

    For more guidance, take a look at my base Genesis theme, EA Genesis Child.

    Downside

    You’ll run into issues if you are using plugins that expect those actions and filters to be there. It won’t break your site, but you’ll lose the functionality those plugins were adding with the actions and filters that used to be on your archive page.

    For instance, my Genesis Grid Loop plugin needs the post summaries to use post_class() so it can add column classes, and expects the Genesis featured image function so it can set the image size for “feature” posts. If you use the Genesis Grid Loop plugin with my EA Genesis Child theme, the archive pages won’t display in a grid as you’d expect. The plugin is using filters that now don’t run on the archive page.

    If you use plugins for assembling your theme customizations, then this is not for you. But if you’re looking for a more modular way to develop your custom Genesis child themes, consider using template parts.

  • Related Posts with SearchWP

    We use SearchWP on almost all the websites we build. It provides search results based on relevancy rather than recency; indexes more attributes like categories, tags, and custom fields; and includes detailed reporting for on-site searches.

    It’s also a great tool for related posts. Instead of showing recent posts in the same category or tag and hoping they are related to the current post, you can ask SearchWP for content that is actually related.

    The Related Content extension adds a metabox for controlling the keywords used. By default it uses the current post’s title as the keyword, but you can modify it to include any keywords you like. It also displays live search results based on those keywords:

    Displaying Related Posts

    There are a few different ways to display the related posts:

    1. SearchWP can auto-append them to the bottom of your posts using their template.
    2. SearchWP can auto-append them to the bottom of your posts using your template, defined in your-theme/searchwp-related/related.php. Here’s more information on building your own template for their template loader.
    3. You define where they appear in your theme and use their template loader, either with their template or your custom template. Here’s a code example.
    4. You define where they appear in your theme and use your own custom query. This gives you the most flexibility over the query and display.

    My Approach

    I like to use #4 and then add a fallback in case SearchWP is ever disabled, or if the keyword search delivers less results than we want displayed.

    I created a be_get_related_posts() function that queries for related using SearchWP Related. If we need more posts, we then query for recent posts in the same primary category using ea_first_term().

    <?php
    /**
    * Get Related Posts
    * @see https://www.billerickson.net/related-posts-with-searchwp/
    *
    */
    function ea_get_related_posts( $related_to_show = false ) {
    $related_to_show = !empty( $related_to_show ) ? intval( $related_to_show ) : get_option( 'posts_per_page' );
    $related = array();
    // Use SearchWP
    if( class_exists( 'SearchWP_Related' ) && count( $related ) < $related_to_show ) {
    // Instantiate SearchWP Related
    $searchwp_related = new SearchWP_Related();
    // Use the keywords as defined in the SearchWP Related meta box
    $keywords = get_post_meta( get_the_ID(), $searchwp_related->meta_key, true );
    if( empty( $keywords ) )
    $keywords = get_the_title();
    $args = array(
    's' => $keywords, // The stored keywords to use
    'engine' => 'default', // the SearchWP engine to use
    'posts_per_page' => $related_to_show - count( $related ),
    'post__not_in' => array( get_the_ID() ),
    'fields' => 'ids',
    );
    // Retrieve Related content for the current post
    $swp_related = $searchwp_related->get( $args );
    if( !empty( $swp_related ) ) {
    $related = array_merge( $related, $swp_related );
    }
    }
    // Fallback, use primary category
    if( count( $related ) < $related_to_show ) {
    $loop = new WP_Query( array(
    'posts_per_page' => $related_to_show - count( $related ),
    'category_name' => ea_first_term( 'category', 'slug' ),
    'post__not_in' => array_merge( $related, array( get_the_ID() ) ),
    'fields' => 'ids',
    ));
    if( ! is_wp_error( $loop ) && !empty( $loop->posts ) )
    $related = array_merge( $related, $loop->posts );
    }
    return $related;
    }
    view raw single.php hosted with ❤ by GitHub

    Then I create a function to display the related posts. We use template parts for reusable blocks of code like post summaries. This allows us to use the same format for listings on archive pages, post listings on landing pages, and related posts on single posts.

    <?php
    /**
    * Related Posts
    * @see https://www.billerickson.net/related-posts-with-searchwp/
    *
    */
    function be_related_posts() {
    $related = ea_get_related_posts( 3 );
    if( empty( $related ) )
    return;
    $loop = new WP_Query( array(
    'post__in' => $related,
    'orderby' => 'post__in',
    ) );
    if( $loop->have_posts() ):
    echo '<section class="related-posts">';
    echo '<h3>Related Posts</h3>';
    while( $loop->have_posts() ): $loop->the_post();
    get_template_part( 'partials/archive', 'related' );
    endwhile;
    echo '</section>';
    endif;
    wp_reset_postdata();
    }
    add_action( 'genesis_after_entry', 'be_related_posts' );
    view raw single.php hosted with ❤ by GitHub

  • Getting the most value from Genesis

    Watch the on-demand webinar

    Includes a 15 minute presentation on the topic below, and 45 minutes of Q&A.

    Watch Now

    What is Genesis?

    Genesis is a theme framework, a base upon which you build your own custom WordPress theme. Genesis helps you can build higher quality themes faster, and is built with a focus on SEO and performance.

    In the same way that WordPress can be extended using hooks and filters inside plugins, Genesis can be extended using hooks and filters inside your child theme.

    Rather than build everything from scratch, you build a child theme with your unique design and features.

    You have complete creative freedom when designing a Genesis website. Genesis imposes no design constrains. Here are some examples of sites we’ve built with Genesis.

    Benefits to using Genesis

    You can focus on what’s unique about your website and rely on Genesis for the common features. You’ll build higher quality websites faster.

    Even when you are building custom features, you can leverage the tools and APIs inside of Genesis to implement these faster:

    • Add your own theme settings using the Theme Settings API (example).
    • Customize breadcrumbs using Breadcrumbs class (example).
    • Use the Markup API to modify any HTML element’s attributes, class, ID, or even the HTML element itself (example).

    As a developer or agency managing multiple projects, a common codebase across all projects will increase your team’s efficiency. When jumping between projects you won’t have to relearn how everything is structured.

    You can also easily reuse code across projects. You can create a library of code snippets, theme files, and plugins with commonly used functionality that can be deployed across multiple projects.

    Another benefit is the ecosystem. As the most popular theme framework on the market, there are many Genesis developers, with lots tutorials, and plenty of free plugins built specifically for Genesis.

    When you launch a custom theme, it becomes frozen in time – the features of the website won’t improve without you making those updates. With Genesis, you silo your custom features and styles in a child theme, allowing the parent theme (Genesis) to continually improve.

    Recent Genesis updates have improved accessibility, added support for new WordPress features, and updated Schema markup for SEO.

    With substantial changes coming to WordPress with Gutenberg, it’s even more important to build upon a platform that will ensure future compatibility.

    How to build with Genesis

    The first step is to decide if you want to customize an existing child theme or start a new child theme. StudioPress has dozens of pre-built child themes, which can be purchased individually or as a package. These themes are available for free to all WPEngine customers. With an hour or two of work you can have a great looking site up and running.

    Most developers and agencies will build custom child themes. A great place to start is the Genesis Sample child theme. After building a few Genesis sites you’ll likely create your own starter child theme with your base styling and common features – here’s mine.

    Styling a Genesis theme is exactly the same as any other WordPress theme. You can write standard CSS or compile SASS.

    What makes Genesis different from a standard theme is how you’ll customize the features and markup on the page. Rather than directly editing the core theme files – like you would in a custom theme – you’ll use hooks and filters to remove or modify core Genesis features, and add your new features.

    Create a template

    Any time you create a template file, you end it with the genesis(); function which builds the page. Here’s the first step in creating a “Pricing” page template:

    <?php
    /**
    * Template Name: Pricing
    **/
    genesis();
    view raw pricing.php hosted with ❤ by GitHub

    You can determine the scope of your customizations by where you place your code. Anything placed in functions.php applies site-wide, while code in a page template only applies to that template.

    We’ll make this template full width using a filter, and add a call to action bar above the site footer using a hook.

    <?php
    /**
    * Template Name: Pricing
    **/
    // Full width template, no sidebar
    add_filter( 'genesis_pre_get_option_site_layout', '__genesis_return_full_width_content' );
    /**
    * Call to Action
    *
    */
    function be_pricing_cta() {
    // CTA code goes here
    }
    add_action( 'genesis_before_footer', 'be_pricing_cta' );
    genesis();
    view raw pricing.php hosted with ❤ by GitHub

    You can use this Visual Hook Reference to see what hooks are available, or dive into the Genesis code itself. Start by looking in these files:

    Change schema for events

    Genesis has support for schema (structured data). The CreativeWork content type is used by default, but you can use the Markup API to change this.

    <?php
    /**
    * Event Schema
    *
    */
    function be_event_schema( $attr ) {
    if ( 'event' == get_post_type() ) {
    $attr['itemtype'] = 'http://schema.org/Event';
    $attr['itemprop'] = '';
    $attr['itemscope'] = 'itemscope';
    }
    return $attr;
    }
    add_filter( 'genesis_attr_entry', 'be_event_schema', 20 );
    view raw functions.php hosted with ❤ by GitHub

    Here’s a full example of changing schema in my events calendar plugin.

    Learn More

    The best way to learn Genesis is to download existing child themes from StudioPress / WPEngine and see how they’re constructed.  You can also review StudioPress documentationsupport forums, and the Facebook group for answers to specific questions.

    For a deeper dive into building Genesis themes, I recommend the video course Building Genesis Child Themes from Scratch by Carrie Dils.

  • Introduction to CSS Grid

    What is CSS Grid?

    CSS Grid is a new layout system in CSS. It’s not a framework or library like Bootstrap – it’s built right in.

    It allows you to take an element on the page and turn it into a “grid” of columns and rows. All of that item’s direct descendants will be “grid items” and placed within those columns and rows.

    It’s not a replacement for Flexbox or floats, but an additional tool that works great in certain contexts. Here’s an article that goes into more detail about when to use Flexbox or Grid.

    How it works

    Let’s start with a simple example. I’m setting up a grid with three columns and three rows, each with a width and height I’ve defined.

    .grid1 {
    	display: grid;
    	grid-template-columns: 50px 100px 150px;
    	grid-template-rows: 150px 100px 50px;
    	grid-gap: 20px;
    }

    Each grid item takes up one spot in the grid, and is sized based its row and column.  Grid items can be any HTML element (div, paragraph, image…) as long as its the direct descendant.

    If you don’t specify a column or row size, it will automatically be sized based on the tallest or widest element in that column / row, respectively.

    Implicit vs Explicit Grid

    The grid made up of rows and columns you have specified is called the explicit grid. In the example above, we defined a 3-row by 3-column explicit grid.

    If you add more items than there are slots in the explicit grid, the browser adds additional rows which are called the implicit grid. Here’s the same example from above but with more grid items:

    .grid2 {
    	display: grid;
    	grid-template-columns: 50px 100px 150px;
    	grid-template-rows: 150px 100px 50px;
    	grid-gap: 20px;
    }
    
    .item {
    	min-height: 40px;
    }

    The height of the implicit grid rows is determined by the tallest element inside the row. In this case, we have a minimum height of 40px on the grid items so the implicit grid rows are 40px.

    You can also specify the height of the automatically added rows using grid-auto-rows.

    .grid3 {
    	display: grid;
    	grid-template-columns: 50px 100px 150px;
    	grid-template-rows: 150px 100px 50px;
    	grid-gap: 20px;
    	grid-auto-rows: 100px;
    }
    
    .item {
    	min-height: 40px;
    }

    Units

    You can use any CSS units you’d like when building your grid, including pixels, rems, and percentages.

    But be careful with percentages – you can easily overflow the grid if you try to add everything up to 100%. You have to consider grid gaps.

    .grid4 {
    	border: 2px solid black;
    	display: grid;
    	grid-template-columns: 75% 25%;
    	grid-gap: 20px;
    }
    
    .item {
    	min-height: 40px;
    }

    If you are using percentages, use auto for one of the values.

    .grid5 {
    	border: 2px solid black;
    	display: grid;
    	grid-template-columns: 75% auto;
    	grid-gap: 20px;
    }
    
    .item {
    	min-height: 40px;
    }

    Fractional Units

    A better approach is to use fractional unit, fr, a new unit added specifically for the grid. This is essentially a “free space” unit and lets you divide the remaining free space based on proportions you define.

    Here’s a simple 3 column grid:

    .grid6 {
    	display: grid;
    	grid-template-columns: 1fr 1fr 1fr;
    	grid-gap: 20px;
    }

    You can change the proportions by changing the number of fractional units assigned to each column.

    .grid7 {
    	display: grid;
    	grid-template-columns: 1fr 2fr 1fr;
    	grid-gap: 20px;
    }

    Free space is calculated after determining the minimum amount of space required for all grid items. Even if you have proportional columns, if one of the grid items is larger than the others it could change the column widths.

    In the below example I’m making the second grid item 300px wide which affects the entire column.

    .grid8 {
    	display: grid;
    	grid-template-columns: 1fr 1fr 1fr;
    	grid-gap: 20px;
    }
    
    .grid8 .item:nth-child(2) {
    	width: 300px;
    	background: #00aa37;
    }

    Repeat

    The repeat() function helps simplify repetitive CSS. For a 6 column grid, instead of typing 1fr 6 times you can use repeat( 6, 1fr ).

    .grid9 {
    	display: grid;
    	grid-template-columns: repeat( 6, 1fr );
    	grid-gap: 12px;
    }

    Spanning multiple columns

    By default, every grid item takes up one column and one row. You can adjust a grid item’s column using the grid-column parameter, and adjust the grid item’s row using grid-row.

    In this example we’re going to make the second item take up two columns.

    .grid10 {
    	display: grid;
    	grid-template-columns: repeat( 4, 1fr );
    	grid-gap: 20px;
    }
    
    .grid10 .item:nth-child(2) {
    	grid-column: span 2;
    	background: #00aa37;
    }

    You can also use this field to specify the starting and/or ending point for the grid item. The above could be rewritten as:

    .grid11 {
    	display: grid;
    	grid-template-columns: repeat( 4, 1fr );
    	grid-gap: 20px;
    }
    
    .grid10 .item:nth-child(2) {
    	grid-column: 2 / 4;
    	background: #00aa37;
    }

    The numbers don’t represent the columns but rather the dividing lines between the columns. An item in the first column would be grid-column: 1 / 2 because it spans from the first to the second line.

    Firefox has a great grid inspector built-in that makes this easier to understand. Here’s a screenshot of the grid in the above example.

    Full width grid items

    You can use negative numbers to count from the other direction. grid-column: 1 / -1 makes an element span the entire width of the grid, regardless of the number of columns. I like to use this for elements that appear at the top and bottom of a grid.

    It’s also useful if you’re changing the number of columns used at different breakpoints. If you specified a specific start and end like grid-column: 1 / span 5 , you would need to change it to grid-column: 1 / span 4 at the breakpoint where you drop the columns by one.

    Let’s say you have an archive of posts. You might have the following markup, with <header> for the page title, <article> for the individual posts, and <nav> for the pagination at the bottom.

    .grid12 {
    	display: grid;
    	grid-template-columns: repeat( 3, 1fr );
    	grid-gap: 12px;
    }
    
    @media only screen and (max-width: 767px) {
    	.grid12 {
    		grid-template-columns: repeat( 2, 1fr );
    	}
    }
    
    
    .grid12 header,
    .grid12 nav {
    	background: #f0f0f0;
    	grid-column: 1 / -1;
    	text-align: center;
    }
    
    .grid12 article {
    	font-size: 12px;
    	color: #fff;
    	padding: 4px;
    }

    Category name goes here

    Lorem ipsum dolor sit amet
    Lorem ipsum dolor sit amet
    Lorem ipsum dolor sit amet
    Lorem ipsum dolor sit amet
    Lorem ipsum dolor sit amet
    Lorem ipsum dolor sit amet
    Lorem ipsum dolor sit amet

    Alignment

    By default, the grid makes all grid items in the same row have the same height, which is either determined by the explicit row height or the height of the largest item. See the examples above to see how grid items all have matching heights.

    You can adjust the grid item alignment on the column axis using align-items. The possible values are: start, end, center, and stretch (default). You can adjust the grid item alignment on the row axis using justify-items, with the same values as above.

    Both of these parameters are added to the grid itself and apply to all grid items. You can adjust an individual grid item’s alignment using align-self and justify-self.

    Take a look at the Complete Guide to Grid for visual examples of all these options.

    .grid13 {
    	display: grid;
    	grid-template-columns: repeat( 2, 1fr );
    	grid-gap: 12px;
    	align-items: center;
    }

    This grid item is smaller and centered.

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quid enim tanto opus est instrumento in optimis artibus comparandis? Ut placet, inquit, etsi enim illud erat aptius, aequum cuique concedere. Dolor ergo, id est summum malum, metuetur semper, etiamsi non aderit; Et quidem iure fortasse, sed tamen non gravissimum est testimonium multitudinis.

    Browser Compatibility

    As of September 2018, CSS Grid is supported globally by 87.25% user’s browsers (see here). For comparison, Flexbox has 95% coverage.

    It might be even higher on your specific site. 97% of visitors to billerickson.net support CSS Grid (see here). Here’s how to determine feature support for your website.

    When determining what CSS features to use, it’s important to consider:

    1. Your actual users and what their browsers support.
    2. If CSS Grid isn’t enabled, how will it affect a user’s ability to browser your site.

    If you’re using the grid for vertical alignment or showing a bulleted list in multiple columns, losing the grid won’t negatively affect the user experience too much. In these cases where it’s a visual enhancement but not structurally important, I recommend writing your CSS using only grid.

    For absolutely essential aspects of your site, like ensuring your content area and sidebar appear next to each other, it’s a good idea to include fallback CSS for browsers that don’t support grid.

    You can use @supports( display: grid ) to see if a browser supports grid. I’ll do my non-grid, typically float-based styling first, then if the browser supports grid I’ll undo those styles and apply my grid styling. They won’t exactly match but the non-grid styling will ensure the site is still functional. Here’s an article I wrote that goes into more detail about Using CSS Grid with IE Fallback.

    .grid14 {
    	overflow: hidden;
    	width: 100%;
    }
    
    .grid14 .item-content-area {
    	float: left;
    	width: 70%;
    }
    
    .grid14 .item-sidebar {
    	float: right;
    	width: 20%;
    }
    
    @supports( display: grid ) {
    	.grid14 {
    		display: grid;
    		grid-template-columns: 1fr 200px;
    		grid-column-gap: 16px;
    	}
    
    	.grid14 .item-content-area,
    	.grid14 .item-sidebar {
    		float: none;
    		width: 100%;
    	}
    }
    This is the content area
    This is the sidebar

    More Resources

    I highly recommend you take Wes Bos’ completely free course on CSS Grid. It provides hands-on tutorials to work through as he illustrates the new features of CSS Grid. With 4 hours of content across 25 videos, you could knock it out in a day, or spread it out over a week or month.

    I also recommend keeping CSS Tricks’ Complete Guide to Grid bookmarked because you’ll be referring to it often.

  • Determining feature support for your specific website

    Can I use… is a wonderful service that shows browser support for specific CSS features globally and in specific countries. For instance, CSS Grid support is 87.25% globally and 90.04% in the US.

    I just discovered you can import your Google Analytics data to see the feature support for your website. Click “Settings”, then the “Import” button under “From Google Analytics”. Once you’ve connected GA and selected which property to show, you’ll see your site data next to the global data.

    CSS Grid has 97% support across the visitors to my site.

  • Building custom pagination links

    WordPress includes the paginate_links() function for building paginated navigation links on archive pages. It works pretty well, but there are two things I’d like to change:

    1. The end_size parameter lets you specify how many of the first and last pages to show (default is 1). But there’s no way to set end_size = 0. When you’re a few pages deep into an archive, you’ll always have “1” at the start and the last page at the end, with dots separating it from the middle links.
    2. The mid_size parameter specifies the number of links to the left and right of the current page. But I’d like to treat them separately, and have more pages after the current page visible. You can’t make your pagination start with the current page and only show next pages.

    While these may seem like minor changes, they would help reduce the varying length of the navigation links.

    Here’s what paginate_links()  generates on the first page of a category archive:

    And here’s what it looks like on the 5th page:

    It takes up too much space and distracts your users from what they care about – the current page, previous link, and next link.

    It’s also difficult to style a navigation area that varies that much. It would look like this on mobile:

    A better way

    I prefer showing the previous/next arrows, current page, and some number of next pages. Here’s what the navigation looks like now:

    Every archive page shows the same number of links. This ensures a consistent look and user experience as they browse your archives. You can choose a number of links that fit on a single line on mobile.

    Use the $settings array at the top to modify the total number of links shown and the previous/next link text.

    <?php
    /**
    * Archive Navigation
    *
    * @author Bill Erickson
    * @see https://www.billerickson.net/custom-pagination-links/
    *
    */
    function ea_archive_navigation() {
    $settings = array(
    'count' => 6,
    'prev_text' => ea_icon( 'arrow-left' ),
    'next_text' => ea_icon( 'arrow-right' )
    );
    global $wp_query;
    $current = max( 1, get_query_var( 'paged' ) );
    $total = $wp_query->max_num_pages;
    $links = array();
    // Offset for next link
    if( $current < $total )
    $settings['count']--;
    // Previous
    if( $current > 1 ) {
    $settings['count']--;
    $links[] = ea_archive_navigation_link( $current - 1, 'prev', $settings['prev_text'] );
    }
    // Current
    $links[] = ea_archive_navigation_link( $current, 'current' );
    // Next Pages
    for( $i = 1; $i < $settings['count']; $i++ ) {
    $page = $current + $i;
    if( $page <= $total ) {
    $links[] = ea_archive_navigation_link( $page );
    }
    }
    // Next
    if( $current < $total ) {
    $links[] = ea_archive_navigation_link( $current + 1, 'next', $settings['next_text'] );
    }
    echo '<nav class="navigation posts-navigation" role="navigation">';
    echo '<h2 class="screen-reader-text">Posts navigation</h2>';
    echo '<div class="nav-links">' . join( '', $links ) . '</div>';
    echo '</nav>';
    }
    add_action( 'tha_content_while_after', 'ea_archive_navigation' );
    /**
    * Archive Navigation Link
    *
    * @author Bill Erickson
    * @see https://www.billerickson.net/custom-pagination-links/
    *
    * @param int $page
    * @param string $class
    * @param string $label
    * @return string $link
    */
    function ea_archive_navigation_link( $page = false, $class = '', $label = '' ) {
    if( ! $page )
    return;
    $classes = array( 'page-numbers' );
    if( !empty( $class ) )
    $classes[] = $class;
    $classes = array_map( 'sanitize_html_class', $classes );
    $label = $label ? $label : $page;
    $link = esc_url_raw( get_pagenum_link( $page ) );
    return '<a class="' . join ( ' ', $classes ) . '" href="' . $link . '">' . $label . '</a>';
    }
    view raw functions.php hosted with ❤ by GitHub

  • Template Parts with Display Posts Shortcode

    Display Posts Shortcode lets you easily display posts based on any criteria you choose without any coding. Here’s the list of available parameters.

    It includes some display options, but it’s unstyled so you’ll need to write CSS to make it look good. It’s difficult to match your theme’s layout for post summaries using just the shortcode and CSS.

    Different styles of post summaries used by this theme

    Styling belongs in a theme

    Your theme already contains the markup and styling for displaying posts. By leveraging it, the shortcode output can match the styling of your site.

    A few years from now when you redesign your site, your shortcodes will automatically use the new post styles defined in that theme.

    Markup in Template Partials

    I use template partials for post layouts in my themes. Here’s the partials directory in my starter theme.

    I use archive.php as the base layout for a post summary. It’s used on archive pages, and often elsewhere like a related posts section after a single post.

    <?php
    /**
    * Archive partial
    *
    * @package StudyFinds2018
    * @author Bill Erickson
    * @since 1.0.0
    * @license GPL-2.0+
    **/
    echo '<article class="post-summary">';
    echo '<a class="entry-image-link" href="' . get_permalink() . '">' . wp_get_attachment_image( ea_entry_image_id(), 'ea_archive' ) . '</a>';
    $term = ea_first_term();
    $category = '<a class="entry-category-link" href="' . get_term_link( $term, 'category' ) . '">' . $term->name . '</a>';
    echo '<div class="entry-info">' . $category . ea_archive_sharing() . '</div>';
    echo '<h2 class="entry-title"><a href="' . get_permalink() . '">' . get_the_title() . '</a></h2>';
    echo '</article>';
    view raw archive.php hosted with ❤ by GitHub

    For areas that require a different output, I create a separate partial. On this theme we displayed featured posts in a rotator, so I created archive-featured.php:

    view raw archive-featured.php hosted with ❤ by GitHub

    I use the same approach when it comes to styling Display Posts Shortcode. I might have two post layouts: large and small. I’ll create dps-large.php and dps-small.php to hold the markup. If the large layout matches the archive layout, you can simply include one into the other:

    <?php
    /**
    * "Large" layout for Display Posts Shortcode
    *
    * @package StudyFinds2018
    * @author Bill Erickson
    * @since 1.0.0
    * @license GPL-2.0+
    **/
    require get_template_directory() . '/partials/archive.php';
    view raw dps-large.php hosted with ❤ by GitHub

    The small layout uses slightly different markup:

    <?php
    /**
    * "Small" layout for Display Posts Shortcode
    *
    * @package StudyFinds2018
    * @author Bill Erickson
    * @since 1.0.0
    * @license GPL-2.0+
    **/
    echo '<article class="post-summary small">';
    echo '<a class="entry-image-link" href="' . get_permalink() . '">' . wp_get_attachment_image( ea_entry_image_id(), 'ea_archive' ) . '</a>';
    echo '<h2 class="entry-title"><a href="' . get_permalink() . '">' . get_the_title() . '</a></h2>';
    echo '</article>';
    view raw dps-small.php hosted with ❤ by GitHub

    I use the “dps” prefix to indicate that these are for Display Posts Shortcode, and to limit which partials can be used by the shortcode.

    Specify a layout in the shortcode

    I add a “layout” parameter to specify which post layout should be used.

    [display-posts layout="large"]

    This is a custom parameter, so add the following to your theme’s functions.php file to make it functional:

    <?php
    /**
    * Template Parts with Display Posts Shortcode
    * @author Bill Erickson
    * @see https://www.billerickson.net/template-parts-with-display-posts-shortcode
    *
    * @param string $output, current output of post
    * @param array $original_atts, original attributes passed to shortcode
    * @return string $output
    */
    function be_dps_template_part( $output, $original_atts ) {
    // Return early if our "layout" attribute is not specified
    if( empty( $original_atts['layout'] ) )
    return $output;
    ob_start();
    get_template_part( 'partials/dps', $original_atts['layout'] );
    $new_output = ob_get_clean();
    if( !empty( $new_output ) )
    $output = $new_output;
    return $output;
    }
    add_action( 'display_posts_shortcode_output', 'be_dps_template_part', 10, 2 );
    view raw functions.php hosted with ❤ by GitHub

    The partials are only used if a layout is specified in the shortcode, and a matching partial is found. This lets you keep using the plugin in other contexts as well, like a bulleted list of recent posts.

    This also saves me from building custom Related / Recent Posts widgets. We simply add a text widget containing the shortcode and it matches the site:

    Display popular content

    If you’re using Shared Counts for social sharing, you can display a list of your most shared content with:

    [display-posts layout="small" orderby="meta_value_num" meta_key="shared_counts_total"]

    You can even get fancy and display the 5 most shared posts published in the last 6 months in the same category as the current post:

    [display-posts layout="small" posts_per_page="5" orderby="meta_value_num" meta_key="shared_counts_total" taxonomy="category" tax_term="current" date_query_after="6 months ago"]

    Find yourself writing the same long shortcode over and over? Create a shortcut argument, then use something like

    [display-posts show_popular="true"]

    For more examples of customizing this plugin, see my Display Posts Shortcode code snippets.

  • 10 ways to speed up your WordPress website

    A slow website is painful to use, less engaging to visitors, and hurts your search engine rankings. I’ll walk you through the tools I use to identify performance problems, and provide ten tips to improve your website’s performance.

    You don’t need a WordPress Developer

    I’ve written this article to help website owners improve their website’s performance on their own. You can implement most of these changes yourself or with your website host’s help.

    You should see substantial speed improvements after implementing even a few of these recommendations. You don’t need to do everything on this list.

    If you’d like to hire a professional, please contact me. Here’s some information on what makes a custom WordPress website different, our process, and how you can monitor your website’s success over the long term.

    Quick Links to the Ten Tips

    1. Caching
    2. Image Compression
    3. High Quality Hosting
    4. Plugin Audit
    5. Minify CSS and JS
    6. Cache static content
    7. Use a CDN
    8. Clean up options table
    9. Decrease markup
    10. Decrease external requests

    Before we get to the tips, I’d like to describe what makes a website slow. I’ll then show you how to diagnose what’s wrong with your website so you know which of these tips will be most effective.


    What makes a WordPress website slow?

    I like to break performance issues into two groups:

    Backend Issues

    I describe “backend issues” as issues that happen before and during the page generation on the server.

    WordPress is a database-driven content management system. Your home page doesn’t exist as a static file on your server. It’s dynamically built using theme files, plugins, and database queries (ex: building menus, post content, sidebar widgets…).

    Backend performance issues are usually caused by:

    • Poor hosting
    • Overloaded server
    • Complicated queries
    • Poorly written theme / plugins

    In the performance report described below, a long Time To First Byte (TTFB) indicates backend issues. TTFB records the time from the initial request to when your server is done processing and starts sending data to the visitor.

    The easiest improvements are to get better hosting and eliminate unnecessary plugins. Beyond that, these issues become more difficult for a non-technical person to solve on their own. Luckily you can cover up much of your backend issues with full page caching.

    Frontend Issues

    I describe “frontend issues” as issues that occur while the page is loading. These issues are usually caused by:

    • Large file sizes (images, CSS, JS)
    • Many files
    • Slow delivery

    These issues are easier to identify, and some are simple to solve, like compressing imagesminifying CSS, and removing unnecessary plugins. It becomes more difficult when your “necessary” plugin list is long and they’re responsible for most of your issues.

    Diagnosing issues

    WebPageTest is my favorite frontend performance testing tool. You can drop in the URL of any website and get specific recommendations on improvements. You can also use Pingdom for a similar report.

    Here’s an example of a speed test of my website.

    They include letter grades at the top to summarize the results (A and B are good, D and F are bad). If you have a D or F for “First Byte Time”, that means you have backend issues. The rest are frontend / hosting issues.

    Also pay attention to the “Fully Loaded” section. This summarizes the total time for your website to load, number of requests (different files that were loaded), total file size of the page, and a “cost” of delivering a site of that size (you want as few dollar signs as possible).

    P3 Profiler lets you do a backend performance test of your site, focusing on the impact each plugin and your theme has on the load time. This is a great tool to quickly identify which plugins are causing issues.

    Query Monitor lets you quickly see the number of queries run on a page, and how long each took to run. You can filter them by type, component (WP core, specific plugins) and sort by most time consuming.


    10 ways to speed up your WordPress website

    1. Caching

    The simplest change most WordPress websites could benefit from is to use full page caching.

    As described above, WordPress goes through a lot of work to build each page of your site. Every time someone asks to see your homepage, it does many queries and runs lots of code across WP core, your theme, and your active plugins.

    But most of the time the page doesn’t change – it’s the same as the last time it was requested. Instead of generating it fresh for every visit, you can build it once, cache it, and serve the cached file every time someone requests that page. The cache expires periodically (every few hours), and is automatically cleared any time you edit the page.

    Even if you have serious backend performance issues, most of your users won’t notice since they’re not interacting with your database at all – they’re downloading the static copy of your site. That isn’t to say you can ignore backend performance issues, but hiding it with caching can be a short-term solution.

    High quality hosts like WPEngine have full page caching built-in, but on other hosts you’ll want to use WP Rocket (my recommendation, $39) or W3 Total Cache (free).

    Even with WPEngine, I recommend WP Rocket because it addresses many of the other performance tips listed below.

    If you’re a WordPress developer, I recommend you also consider fragment caching for complex queries or external API requests. While the full page cache might get rebuilt 5-10 times a day, you can cache the most expensive queries for longer using a transient, specifying exactly how often you want to refresh it. Here’s a great article on WordPress transients.

    Let’s say your homepage uses a complex query to gather featured posts. Instead of running this every time the homepage is built, you could use a transient to refresh it once a day.

    2. Image Compression

    This is such an easy win! Most websites are using large, uncompressed images. You can reduce the image file size by 20-80% with no visual change. WebPageTest will tell you your potential savings from compression on the performance review tab (screenshot).

    Go to your website right now and save an image. Then go to TinyPNG.com and upload it. See how much you could save. Now download the compressed image and open it with the original. Can you tell a difference?

    You could manually upload every image to TinyPNG and download it before uploading it to your site, but that’s a hassle. I recommend installing the TinyPNG WordPress plugin and getting an API key. You get 500 free compressions per month which is plenty for most of my clients. You can also try other plugins like WP Smush and Imagify.

    If you’re loading lots of images on your pages, you may consider lazy loading, which will wait to load the image when the visitor is close to seeing it.

    You should also make sure you’re loading properly sized images throughout your site. I saw a site once with an archive page that had 300px wide images, but it was loading the original image file which was often 1600px wide or more.

    3. High Quality Hosting

    Your website can only be as fast as the server you host it on. If you’re using cheap, generic, $2/month hosting, you could follow all of these tips and still have trouble reaching the level of performance you’d like.

    I recommend using a WordPress managed host like WPEngine ($35/month), LiquidWeb ($99/month), or SiteGround ($12/month). Here’s more information on my hosting recommendations.

    Make sure your host is using PHP 7 or higher. Upgrading to PHP 7 will give you a large performance boost without any other changes on your site. Use the Display PHP Version plugin to find out what version of PHP you currently have, and the PHP Compatibility Checker plugin to ensure your current theme and plugins are compatible.

    4. Plugin Audit

    You’re probably aware that some of your plugins are causing performance issues, but not sure which ones. You can do a simple plugin audit yourself, or hire a WordPress developer, or ask your website’s host. Here’s Chris Lema from LiquidWeb:

    First, clone your site to a staging environment. This will allow you to test changes without affecting your live website.

    Go to the plugins page in the backend of your website. Deactivate any plugins you no longer use. Make a list of the active plugins that are absolutely essential to your site (ex: your membership plugin), those that provide a feature you need but aren’t necessarily unique (ex: social sharing plugins), and those that are optional.

    Use P3 Profiler to see how each plugin affects the load time of your site. Also view the source code of the key pages on your site and search for wp-content/plugins to see the number and size of the CSS and JS files loaded by that plugin.

    Browse your staging site with Debug Bar and Query Monitor, looking for errors and large / excessive queries. Dig into the details to determine which plugins are responsible for these errors & queries.

    Remove all of the optional plugins that negatively affect performance or are loading too many assets. For the non-unique plugins that negatively affect performance, research other plugins that could provide similar functionality.

    Using the staging site, disable plugins to see their affect on your site. Try installing alternative plugins and re-running your performance tests to see the effect of the change.

    Sometimes you’ll be lucky and the results are straightforward. Some unused plugin is responsible for 80% of your load time, so you simply remove it. Other times you may need to hire a WordPress developer to do more extensive auditing and make recommendations for improvements.

    Here’s a list of the plugins I use on most websites, which help maintain or improve the overall performance of the site.

    5. Minify CSS & JavaScript

    Once you’ve eliminated all the plugins you don’t need, it’s time to optimize the ones you have.

    Plugins and themes load CSS and JS files to implement their functionality. They often leave these files “human readable” with lots of space and inline comments. This increases the file size you’re serving up for no good reason – your site visitors don’t need to read the CSS or JS files. To “minify” the files is to remove the extraneous data, often reducing the file size by 25-50%.

    Some caching plugins will minify files for you, like WP Rocket. You can also use a separate plugin for this, like Autoptimize. If you’re using CloudFlare as a CDN (see below), they will also minify your assets for you.

    You should have no problem combining CSS files. JavaScript files can be a bit more tricky. You may have conflicts combining certain JavaScript files. Some plugins may be adding JavaScript in the head of the page, but your combined file appears in the footer, which causes the plugin’s functionality to not work. If you do plan to combine JS files, test it extensively on your staging site.

    6. Cache static content

    Now that we’ve minimized the size and number of assets loading on your site, we should tell browsers to keep those locally cached for a long time.

    If you’re using WP Rocket, it will take care of this for you. If you need to manually implement it yourself, add the following to your .htaccess file, or ask your host to do it for you:

    <IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/gif A31536000
    ExpiresByType image/jpg A31536000
    ExpiresByType image/jpeg A31536000
    ExpiresByType image/png A31536000
    ExpiresByType image/bmp A31536000
    ExpiresByType text/css A31536000
    ExpiresByType text/javascript A31536000
    ExpiresByType application/javascript A31536000
    ExpiresByType application/x-javascript A31536000
    </IfModule>
    

    7. Use a CDN

    Your images, CSS, and JS files represent the majority of your site’s file size. You can deliver these much faster by offloading them to a CDN. A Content Delivery Network (CDN) is a geographically diverse group of servers designed specifically to deliver assets like these.

    CloudFlare offers a CDN as part of their free plan. They also offer many other performance-enhancing features, so are worth reviewing. Here’s a detailed article reviewing CloudFlare’s performance offerings.

    WP Rocket integrates directly with CloudFlare, so if you’re using WP Rocket, follow this tutorial to set up Cloudflare CDN. WP Rocket also integrates with many other CDNs.

    If you’re using WPEngine for hosting, they include an optional CDN with all their plans. Here’s more information on WPEngine’s CDN, and information on integrating WPEngine’s CDN with WP Rocket.

    Jetpack also includes a CDN for images, but not your other assets. If you’re using this plugin, make sure “Serve images from our servers” is checked (it’s on by default). Here’s more information about Jetpack’s Image CDN.

    8. Clean up options table

    This is a technical change that should only be done by a developer.

    The options table is where WordPress core and plugins store site-wide data. Much of this data is set to “autoload”, which means it is loaded on every page of your site.

    On older sites you’ll often find the options table filled with data for plugins no longer active. When I’m redesigning a site, I try to always review the options table and prune out the data associated with plugins no longer used.

    The best place to start is to find large options and the largest autoloaded options. Here’s an example of the 10 largest autoloaded options on a site I’m redesigning, and what I’m planning to remove:

    1. Remove GooglePublisherPlugin – This is by far the largest option, ten times larger than #2. This plugin isn’t even installed on the site and looks like it’s no longer active.
    2. Keep rewrite_rules – This is a core WordPress option that tells WordPress how you’d like your permalinks.
    3. Keep wp_ubwqqt_user_roles – This is a core WP option that describes the roles for registered users in the site
    4. Remove yuzo_related_post_options – We’re building our own related posts query in the theme and won’t be using the Yuzo plugin.
    5. Remove redux_builder_amp – We’re not using the AMP plugin that created this option
    6. Keep widget_custom_html – This is a core WP option
    7. Keep wpseo_titles – This is set by Yoast SEO (formerly WordPress SEO)
    8. Keep cron – This is a core WP option
    9. Remove mashsb_settings – We replaced the Mash Share Buttons plugin with Shared Counts
    10. Remove sm_options – This is from an unused plugin (Google XML Sitemaps), which the client replaced a while ago with similar functionality in Yoast SEO

    9. Decrease markup

    One often overlooked area of optimization is the HTML markup. View the source of your key pages and look at the underlying markup. Are you surprised by how much HTML is associated with certain features of your site? Could those features be built more efficiently, or are they necessary at all?

    WordPress is quite generous with the CSS classes it provides, and they can add up. I worked on a website a few years ago that had a large menu (about 80 items), and roughly 30% of the overall HTML markup was just the menu. By decreasing the unnecessary CSS classes, we were able to drastically shrink the markup. Depending on the situation, I whitelist menu item classes or blacklist menu item classes. The blacklist approach ensures the “Custom CSS Class” feature in the menu editor continues to work, so is best if you are adding custom classes. You can do the same for post classes.

    Once you’ve removed the unnecessary markup, you can compress the remaining HTML using WP Rocket or Autoptimize. Like CSS/JS minification, HTML often has extra space and comments that be safely removed, reducing the size of the page without removing any actual HTML markup.

    10. Decrease external requests

    One final way to improve the speed of your site is to reduce the requests your site makes for external resources. You should try to reduce total requests on your site as well, but external requests require an additional DNS lookup and you can’t improve the speed of a resource you don’t control.

    When doing a performance review of a client site recently, we found their Twitter widget showing recent tweets was slowing the site down quite a bit. It was loading lots of images, about 30 totaling 2.9MB. By removing the Twitter widget we dropped the page size by more than 50% and cut the page load time to less than 2 seconds.

    If you have Gravatars enabled (Globally Recognized Avatars), you could be loading a separate image for every comment on your site, drastically increasing the size and load time of your popular posts.  Go to Settings > Discussion and uncheck “Show Avatars” to to disable them.

    Many social sharing plugins load their buttons directly from the social networks, increasing the number of external requests. Shared Counts uses on-site buttons and leverages smart caching to to minimize the number of requests required to retrieve share counts.

    You can use the Domains tab of the WebPageTest report to see the number of requests you’re making across each domain.

    Your Turn

    Do you have any tips to improve site speed? Share them in the comments below.

Bill Erickson

Bill Erickson is a freelance WordPress developer and a contributing developer to the Genesis framework. For the past 14 years he has worked with attorneys, publishers, corporations, and non-profits, building custom websites tailored to their needs and goals.

Ready to upgrade your website?

I build custom WordPress websites that look great and are easy to manage.

Let's Talk