Sage 9 Load Google Fonts

To load Google web fonts asynchronously in Roots Sage you can use the Typekit Web Font Loader and Google fonts of your choice. It allows you to load fonts with ease and async.

Web Font Loader

The script below will load two fonts of my choice. It needs to be added to main.js and then loaded into Sage using yarn build:production or yarn build. It will then add new html tag classes you can use besides just assigning the newly available fonts to text.

// Load some Google Fonts asynchrously 
// Typekit Web Font Loader loads latest 1.x version

window.WebFontConfig = {  google: { families: [ 'Raleway:300,400,400i,700', 'Open+Sans:300,400,400i,700' ] },};
(function() {  var wf = document.createElement('script');  
wf.src = '';  
wf.type = 'text/javascript';  wf.async = 'true';  
var s = document.getElementsByTagName('script')[0];  
s.parentNode.insertBefore(wf, s);

Google Fonts

Google Fonts of your choice you can get at . There you can choose a font and the font weights you would like to work with. That is how we picked a few for Raleway and Open Sans in the example. But you are free to pick your own of course.

Font Styling

To add fonts to headers, ps and such you can add styling to resources/assets/styles/components/_type.scss . We used something like this for starters:

.home {  h1,  h2,  h3 {    font-family: Raleway, sans-serif;  }
  .h2,  h2 {    font-size: 2.5rem;  }}

Convert Visual Composer Based WordPress Theme for speed

Each week we answer one or multiple Upwork job vacancy questions here on WP Villain. Here is Post I on a WordPress theme built with Visual Composer that has become slow and needs to be converted into a faster site with a better theme setup.

We are currently running a WordPress theme that was heavily modified CSS and utilizes Visual Composer. We need someone to remake a theme using the design we currently have that does not use VC or performs better for Google speed.

Visual Composer

Visual Composer is a great page builder to quickly set up beautiful pages but it takes up a lot of space with short codes and special tags, embedded code, scripts an such. And all this in the end makes many pages on WordPress websites very slow. And, if you ever want to move away from them you are stuck with a lot of custom code tags in your posts and pages that won’t work anymore and will just be printed. It also often conflicts with plugins like Yoast SEO or at least has in the past. That is why we decided a long time ago to dump Visual Composer and either go for a custom code theme or use better page or site builders such as Elementor or Oxygen.

Migrating from Visual Composer

Migrating from Visual Composer to another page builder or simply the WordPress WYSIWYG Editor or Gutenberg is painful. This as it will leave behind all its short codes. And all the functionality that was loaded with it will disappear. So always do this with a local copy and clean it up and find replacement for what was used before.

Theme Choice

You will be able to choose a new turnkey theme or start out with a custom theme, a theme from scratch. The latter is better as you will be able to customize all but will require a lot more work as all will be custom. The former is set up for a large part already so will give you a head start.

Sage Starter Theme

The Sage starter theme is a wonder base theme that works well with many CSS frameworks such as Tailwind, Bootstrap and Foundation, works with a Laravel style file system and view caching and that allows to quickly build solid themes that load fast. It also has built in tools to optimize code, images and concatenate and minify ll the code to loads things way faster. So this would be an option. But it would require quite a bit of work to convert heavily Visual Composer customized pages into custom theme pages. And there may not be budget for that.

Jupiter X and Elementor

With the Jupiter X theme and Template and the use of Elementor Page Builder we would be able to build better pages more quickly that should also load better. Jupiter X offers many templates out of the box that are a great start for many businesses. They also offer a nice range of plugins that make our lives easier. Plugins to add custom post types, custom fields, sliders and much more. It would still take quite some time, but probably less than doing a theme from scratch. Will it be better? It will be better than your VC setup for sure, but the Sage setup will be better and faster.

Choices to make

So what should you choose? A custom theme, one with Sage working with us or a ready made theme we will customize into a site like your current with Elementor? Well, that depends on the budget. A Sage set up will be more work as it will be a new customized theme, possibly using Elementor or Advanced Custom Field, but built form scratch. When you start with Jupiter X you will have a base and styles to work with and you can customize those. You should have an advantage with existing layouts. This depends on how close your base template is to the site you currently have though.

Main Take

So can Jupiter X match site quite well layout wise and or style wise? Well, if so go for that. If not go for the Sage starter theme. We believe in the end Sage will pay off as it will be tailor made, swift and completely as you want it. With Jupiter X as you have a base and several plugins including a page builder that is always somewhat tougher and you are more limited by the foundation you start with. Sage is more of a clean slate you can tailor to your satisfaction.

Sage 9 Error: Failed because of a stylelint error.

If you get an error like

$ webpack --progress --config resources/assets/build/webpack.config.js
Error: Failed because of a stylelint error.

at linter.then (/Users/jasper/code/alarsite/wp-content/themes/alar/node_modules/stylelint-webpack-plugin/lib/run-compilation.js:39:14)
error Command failed with exit code 1.
info Visit for documentation about this command.

And you wonder what the issue is just run the following command

yarn run lint:styles

That will tell you what the issue is.

Sage 9 Beta 3 WooCommerce Setup

Since I wrote my last post on setting up WooCommerce in Sage 9 things have changed once again. Sage 9 beta 3 has adjusted its structure and looks even more like a Laravel app. And WooCommerce has also been adjusted and since WooCommerce 3.0 several patches have been added. So let’s discuss the Sage 9 Beta 3 WooCommerce Setup shall we?

Sage Views

There are no longer template files anymore. That is, there are, but they are now all added under /resources/views . Not under templates anymore. Here is the structure now:

site/web/app/themes/sage/resources/views $ tree -L 2
├── 404.blade.php
├── archive-product.php
├── index.blade.php
├── layouts
│   └── app.blade.php
├── page.blade.php
├── partials
│   ├── comments.blade.php
│   ├── content-page.blade.php
│   ├── content-search.blade.php
│   ├── content-single.blade.php
│   ├── content.blade.php
│   ├── entry-meta.blade.php
│   ├── footer.blade.php
│   ├── head.blade.php
│   ├── header.blade.php
│   ├── page-header.blade.php
│   └── sidebar.blade.php
├── search.blade.php
├── single-product.php
├── single.blade.php
├── template-custom.blade.php
├── woocommerce
│   ├── archive-product.php
│   └── single-product.php
└── woocommerce.blade.php

As you can see all .blade.php files are now under resources/views like they are in a Laravel app.

WooCommerce Templates

We still seem to need to add archive-product.php and single-product.php to make WooCommerce work properly and have the content we add for WooCommerce loaded there. These seem to work from the views directory or views/woocommerce. But we do now have a way to use Blade templating by making them load woocommerce.blade.php.

This we can do with a simple:

<?php echo App\Template('woocommerce'); ?>

WooCommerce Blade file

The woocommerce.blade.php file is where we do all our magic and CAN use Blade. For a basic display we add:


This so we can load the basic app layout and the general WooCommerce content using:


As you can see this is a great step forward integrating Blade into WooCommerce!

Bootstrap Navigation in Sage 9

If you are working with Bootstrap and like most Sage developers are brave enough to work with the Bootstrap 4 beta with all it’s goodies you will often want to add a Bootstrap menu. So how is a Bootstrap Navigation in Sage 9 done? Here is how.

Sage 9 and Bootstrap

When you install Sage 9 do remember to pick Bootstrap as your CSS framework. This way the latest Bootstrap 4 will be added as the framework of choice. And that way the scss files needed to make Bootstrap 4 run will be added to your theme.

Partials Header

To load the navigation with the proper html tags edit the default templates/partials/header.blade.php and make it have this code for content:

<header class="banner">
 <div class="container">
 <nav class="navbar navbar-toggleable-md navbar-light bg-faded">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
 data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
 aria-label="Toggle navigation">
 <span class="navbar-toggler-icon"></span>
<a class="navbar-brand" href="{{ home_url('/') }}">{{ get_bloginfo('name', 'display') }}</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
 @if (has_nav_menu('primary_navigation'))
 {!! wp_nav_menu(['theme_location' => 'primary_navigation', 'walker' => new wp_bootstrap_navwalker(), 'menu_class' => 'navbar-nav nav pull-right']) !!}
 <!-- <form class="form-inline collapse navbar-collapse pull-right">
 <button class="btn btn-outline-success" type="button">Main button</button>
 </form> -->

This will make it work with the right html tags as well as CSS classes and IDs.

Custom Walker

You will of course also be needing a custom walker. And that walker should be loaded in the theme. In scr/  (app/ in beta 3) we add wp_bootstrap_navwalker.php:


 * Class Name: wp_bootstrap_navwalker
 * GitHub URI:
 * Description: A custom WordPress nav walker class to implement the Bootstrap 4 navigation style in a custom theme using the WordPress built in menu manager.
 * Version: 2.0.4
 * Author: Edward McIntyre - @twittem
 * License: GPL-2.0+
 * License URI:

class wp_bootstrap_navwalker extends Walker_Nav_Menu {

 * @see Walker::start_lvl()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of page. Used for padding.
 public function start_lvl( &$output, $depth = 0, $args = array() ) {
 $indent = str_repeat( "\t", $depth );
 $output .= "\n$indent<div role=\"menu\" class=\" dropdown-menu\">\n";

 * Ends the list of after the elements are added.
 * @see Walker::end_lvl()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of menu item. Used for padding.
 * @param array $args An array of arguments. @see wp_nav_menu()
 public function end_lvl( &$output, $depth = 0, $args = array() ) {
 $indent = str_repeat("\t", $depth);
 $output .= "$indent</div>\n";

 * Start the element output.
 * @see Walker::start_el()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Menu item data object.
 * @param int $depth Depth of menu item. Used for padding.
 * @param array $args An array of arguments. @see wp_nav_menu()
 * @param int $id Current item ID.
 public function end_el( &$output, $item, $depth = 0, $args = array() ) {
 if($depth === 1){
 if(strcasecmp( $item->attr_title, 'divider' ) == 0 || strcasecmp( $item->title, 'divider') == 0) {
 $output .= '</div>';
 }else if ($depth === 1 && (strcasecmp( $item->attr_title, 'header') == 0 && $depth === 1)) {
 $output .= '</h6>';
 $output .= '</li>';

 * @see Walker::start_el()
 * @since 3.0.0
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Menu item data object.
 * @param int $depth Depth of menu item. Used for padding.
 * @param int $current_page Menu item ID.
 * @param object $args
 public function start_el( &$output, $item, $depth = 0, $args = array(), $id = 0 ) {
 $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

 * Dividers, Headers or Disabled
 * =============================
 * Determine whether the item is a Divider, Header, Disabled or regular
 * menu item. To prevent errors we use the strcasecmp() function to so a
 * comparison that is not case sensitive. The strcasecmp() function returns
 * a 0 if the strings are equal.

//( strcasecmp($item->attr_title, 'disabled' ) == 0 ) 
 if($depth === 1 && (strcasecmp( $item->attr_title, 'divider' ) == 0 || strcasecmp( $item->title, 'divider') == 0)) {
 $output .= $indent . '<div class="dropdown-divider">';
 }else if ((strcasecmp( $item->attr_title, 'header') == 0 && $depth === 1) && $depth === 1){
 $output .= $indent . '<h6 class="dropdown-header">' . esc_attr( $item->title );
 $class_names = $value = '';

$classes = empty( $item->classes ) ? array() : (array) $item->classes;
 $atts = array();
 $atts['title'] = ! empty( $item->title ) ? $item->title : '';
 $atts['target'] = ! empty( $item->target ) ? $item->target : '';
 $atts['rel'] = ! empty( $item->xfn ) ? $item->xfn : '';
 $atts['href'] = ! empty( $item->url ) ? $item->url : '';

$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
 if ( in_array( 'current-menu-item', $classes ) )
 $classes[] = ' active';

if($depth === 0){
 $classes[] = 'nav-item';
 $classes[] = 'nav-item-' . $item->ID;
 $atts['class'] = 'nav-link';
 if ( $args->has_children ){
 $classes[] = ' dropdown';
 $atts['href'] = '#';
 $atts['data-toggle'] = 'dropdown';
 $atts['class'] = 'dropdown-toggle nav-link';
 $atts['role'] = 'button';
 $atts['aria-haspopup'] = 'true';
 $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
 $class_names = $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';

$id = $id ? ' id="' . esc_attr( $id ) . '"' : '';
 $output .= $indent . '<li' . $id . $value . $class_names .'>';
 $classes[] = 'dropdown-item';
 $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
 $atts['class'] = $class_names;
 $atts['id'] = $id;
 $atts = apply_filters( 'nav_menu_link_attributes', $atts, $item, $args );

$attributes = '';
 foreach ( $atts as $attr => $value ) {
 if ( ! empty( $value ) ) {
 $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
 $attributes .= ' ' . $attr . '="' . $value . '"';

$item_output = $args->before;
 $item_output .= '<a'. $attributes .'>';
 * Icons
 * ===========
 * Since the the menu item is NOT a Divider or Header we check the see
 * if there is a value in the attr_title property. If the attr_title
 * property is NOT null we apply it as the class name for the icon
 if ( ! empty( $item->attr_title ) ){
 $item_output .= '<span class="' . esc_attr( $item->attr_title ) . '"></span>&nbsp;';

$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
 $item_output .= '</a>';
 $item_output .= $args->after;

$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );

 * Traverse elements to create list from elements.
 * Display one element if the element doesn't have any children otherwise,
 * display the element and its children. Will only traverse up to the max
 * depth and no ignore elements under that depth.
 * This method shouldn't be called directly, use the walk() method instead.
 * @see Walker::start_el()
 * @since 2.5.0
 * @param object $element Data object
 * @param array $children_elements List of elements to continue traversing.
 * @param int $max_depth Max depth to traverse.
 * @param int $depth Depth of current element.
 * @param array $args
 * @param string $output Passed by reference. Used to append additional content.
 * @return null Null on failure with no changes to parameters.
 public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
 if ( ! $element )

$id_field = $this->db_fields['id'];

// Display this element.
 if ( is_object( $args[0] ) )
 $args[0]->has_children = ! empty( $children_elements[ $element->$id_field ] );

parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );

 * Menu Fallback
 * =============
 * If this function is assigned to the wp_nav_menu's fallback_cb variable
 * and a manu has not been assigned to the theme location in the WordPress
 * menu manager the function with display nothing to a non-logged in user,
 * and will add a link to the WordPress menu manager if logged in as an admin.
 * @param array $args passed from the wp_nav_menu function.
 public static function fallback( $args ) {
 if ( current_user_can( 'manage_options' ) ) {

extract( $args );

$fb_output = null;

if ( $container ) {
 $fb_output = '<' . $container;

if ( $container_id )
 $fb_output .= ' id="' . $container_id . '"';

if ( $container_class )
 $fb_output .= ' class="' . $container_class . '"';

$fb_output .= '>';

$fb_output .= '<ul';

if ( $menu_id )
 $fb_output .= ' id="' . $menu_id . '"';

if ( $menu_class )
 $fb_output .= ' class="' . $menu_class . '"';

$fb_output .= '>';
 $fb_output .= '<li><a href="' . admin_url( 'nav-menus.php' ) . '">Add a menu</a></li>';
 $fb_output .= '</ul>';

if ( $container )
 $fb_output .= '</' . $container . '>';

echo $fb_output;

This one is from a fork of the original Twittem repo. It has been created by @sebakerckhof . A bit older now, but despite some BS changes it works well.

Including Custom Walker

To load it inside the theme you should have this in resources/functions.php (in latest Sage 9 beta3)

array_map(function ($file) use ($sage_error) {
 $file = "src/{$file}.php";
 if (!locate_template($file, true, true)) {
 $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
}, ['helpers', 'setup', 'filters', 'admin', 'wp_bootstrap_navwalker']);

The array_map(function..) is already there. You just need to add the file to the array there to have it included. This is different from working in Sage 9 so it may be a little getting used to.

Once you have done that the Bootstrap Navigation in Sage 9 will be a fact. Of course plenty of styling to be done and perhaps you want other classes for different displays within the page. But I am sure you will be able to work that out )) .

Sage 9 WooCommerce Setup

To set up Sage 9 with WooCommerce things have to be done slightly different from the setup in Sage 8. This mainly as Sage 9 works with the Laravel Blade templating system. It is mostly the same though. Here is how the Sage 9 WooCommerce setup is done.

Note Also see Sage 9 Beta 3 follow-up post

Steps to take

  1. Create archive-product.php and single-product.php in the theme root with <?php woocommerce_content(); ?>
  2. Add theme support in sage/src/setup.php (moved to app/ since beta 3) with add_theme_support('woocommerce');
  3. Add woocommerce.blade.php inside templates/woocommerce
  4. Add dummy content
  5. Start styling

WooCommerce Files

The WooCommerce files that are necessary as a bare minimum you can just copy from the plugin or as stated you can just use:

<?php woocommerce_content(); ?>

inside empty newly created files. The latter being much cleaner to start with. So go for that.

WooCommerce Theme Support

For the WooCommerce theme support you can use this snippet:

 * Enable WooCommerce Support in Theme
 * @link

Add it to your setup.php file inside theme/src  (moved to app/ since beta 3). This will activate WooCommerce support for your theme.

Missing Files Notices

Then you will still have notices like header.php missing:

Notice: Theme without header.php is <strong>deprecated</strong> since version 3.0.0 with no alternative available. Please include a header.php template in your theme. in /srv/www/ on line 3959

as well as footer.php and sidebar.php . You can add blank ones for starters to deal with these notices and add files later. They can be added to the root of the theme, but in the case of Sage you should really put them in the templates folder.

Mind you this only happens on a clean setup. Why these notices are shown and why these files are not added to the theme can be read here.

Dummy Content

Dummy content is inside the WooCommerce theme. So you can import it using the WooCommerce importer. Do not download it from another site as that fails most of the time. I did and it failed to import the images resulting in more notices.

Next Steps

The next steps for the Sage 9 WooCommerce Setup would be adding serious styling. But as usual it will be better to first work on the general header and theme navigation, footer and general pages before moving on to that. In Sage 9 you can choose your own CSS framework, Foundation or Bootstrap:

Select a CSS Framework



For my next theme I will be choosing Bootstrap. Once I have done some more work on it I will add another post with details on the single-product.php, product-archive.php and so on styling.

Advanced Custom Fields in Sage

Sometimes you need to work with custom fields for a theme. This as you work with a template that consists of many different content fields and that tend to need different layouts too. And sometimes you do not feel like doing it all from scratch because you are lazy, or because you simply have more to do. Then Advanced Custom Fields or ACF is your friend. Simply the best GUI plugin to deal with these pesky meta boxes. So how do you put Advanced Custom Fields in Sage?

Adding ACF to Sage

Just add the entire folder inside the theme root and call it acf. If you pick another directory name make sure to change stuff accordingly here below. ACF as a name is nice and sweet and is what the guys at Advanced Custom Fields use as well.

advanced custom fields directory


Loading Advanced Custom Fields in Sage

Well just like in any other theme you would use the snippet suggested by ACF here. Only you would need to use namespaces like you would for working with actions and filters in general as Sage works with those. So after adjusting the snippet you would have:


// 1. customize ACF path
add_filter('acf/settings/path', __NAMESPACE__. '\my_acf_settings_path');
function my_acf_settings_path( $path ) {
 // update path
 $path = get_stylesheet_directory() . '/acf/';
 // return
 return $path; 
// 2. customize ACF dir
add_filter('acf/settings/dir', __NAMESPACE__ . '\my_acf_settings_dir');
function my_acf_settings_dir( $dir ) {
 // update path
 $dir = get_stylesheet_directory_uri() . '/acf/';
 // return
 return $dir; 
// 3. Hide ACF field group menu item
//add_filter('acf/settings/show_admin', '__return_false');
// 4. Include ACF
include_once( get_stylesheet_directory() . '/acf/acf.php' );

this snippet I would add to extras.php which is automatically included in the functions.php. Best place for custom code besides custom libraries or the customizer for which new files / directories would be better.

Well, once all that is done and you reload the admin you will see you are up and running. Go ahead and create your custom fields and start adding them to your pages or templates. Have fun!

NB You can uncomment hiding AC in admin for live sites where you do not want clients to play with it.

Sage Theme Sidebars – How to set sidebars up properly in Sage

Sage Theme Sidebars are WordPress sidebars set up DRY using the theme’s wrapper. Basically sidebars done the right way. You will need to add the code to register a new sidebar and add the code to the theme file where you want the sidebar to be loaded. Just like you would normally add a sidebar to a regular theme. There are however a few things you need to look at. These include:

  • the location to add the various code snippets,
  • namespaces and the
  • Sage specific loading and hiding of the general sidebar.

OK let’s dive into the matter and take it a step at the time.

Registering Sidebars in Sage

In your theme folder under theme/lib you can find the file setup.php. It combines what used to be called config.php and init.php. It basically is a config file containing code to add thumbnail sizes, sidebar, menus, theme assets as well as some tweaks when you use soil. Inside this file you will find this piece of code:

function widgets_init() {

 'name' => __('Primary', 'sage'),
 'id' => 'sidebar-primary',
 'before_widget' => '<section class="widget %1$s %2$s">',
 'after_widget' => '</section>',
 'before_title' => '<h3>',
 'after_title' => '</h3>'
 'name' => __('Footer', 'sage'),
 'id' => 'sidebar-footer',
 'before_widget' => '<section class="widget %1$s %2$s">',
 'after_widget' => '</section>',
 'before_title' => '<h3>',
 'after_title' => '</h3>'
add_action('widgets_init', __NAMESPACE__ . '\\widgets_init')

This is where Sage registers its sidebars. Here you could copy the code of one of the Sage theme sidebars and customize it to your liking. The action to add these sidebars uses coding you may not be familiar with as Sage uses namespaces which are available since PHP 5.3. Really useful to organize code the way you organize files in folders.

Loading Setup File

Also, as you may have noticed, the sidebars are not added to functions.php, but to a separate file. In functions.php this file is loaded with the following array:

$sage_includes = [
 'lib/assets.php', // Scripts and stylesheets
 'lib/extras.php', // Custom functions 
 'lib/media.php', // Media functions
 'lib/setup.php', // Theme setup
 'lib/titles.php', // Page titles
 'lib/wrapper.php', // Theme wrapper class
 'lib/customizer.php', // Theme customizer 
 'lib/widget.php' // Theme customizer

Sage does it this way to not overload the functions.php with all code and organize all more logically. And I like it. Otherwise I would not be using this awesome starters theme in the first place.

NB widgets_init() might be confusing here as it is when you start out with sidebars or asides. But sidebars load widgets after all so that is the function we need and action we use to load all the registered sidebars.

Displaying the General Sidebars

Displaying the sidebars is just like with any other sidebar. You could use the code:

<?php if ( dynamic_sidebar('example_widget_area_name') ) : else : endif; ?>

in any theme file for example. However, Sage does apply the DRY principle and does not like to repeat itself. So it uses base.php to load all code that is used cross files to load template files with or without a general sidebar (vertical one). Here a snippet from that bare file:

<div class="wrap container" role="document">
 <div class="content row">
 <main class="main">
 <?php include Wrapper\template_path(); ?>
 </main><!-- /.main -->
 <?php if (Setup\display_sidebar()) : ?>
 <aside class="sidebar">
 <?php include Wrapper\sidebar_path(); ?>
 </aside><!-- /.sidebar -->
 <?php endif; ?>
 </div><!-- /.content -->
 </div><!-- /.wrap -->

As you can see it determines whether or not the sidebar should be loaded. This being the general sidebar.

Sidebars File

And then the base.php file  load the sidebars from templates/sidebar.php. It does this using the sidebar_path() function. That function is inside lib.wrapper.php and states:

function sidebar_path() {
 return new SageWrapping('templates/sidebar.php');

This loads a new instance of SageWrapping with the sidebar file. I could write a whole section on the wrapper, but I won’t here to keep this article within bounds. More on the Sage wrapper here. In that file sidebar.php you will initially find only:

<?php dynamic_sidebar('sidebar-primary'); ?>

This is the main sidebar that is loaded selectively using the soon to be mentioned display_sidebar() function. One for my theme in progress has:

 <?php if (!is_woocommerce()) : ?>
 <?php dynamic_sidebar('sidebar-primary'); ?>
 <?php endif; ?> 
 <?php if (taxonomy_exists('product_cat')) : ?>
 <?php dynamic_sidebar('shop-main'); ?>
 <?php endif; ?>

As you can see I added some WooCommerce specific code for loading Sage theme sidebars for the shop part of things.

Hiding the Main Sidebar

Sage has an added function / filter you can easily use to show or hide the general sidebar that comes with the theme. It is inside the file lib/setup.php:

 * Determine which pages should NOT display the sidebar
function display_sidebar() {
 static $display;
 isset($display) || $display = !in_array(true, [
 // The sidebar will NOT be displayed if ANY of the following return true.
 // @link
 return apply_filters('sage/display_sidebar', $display);

You just add the WordPress conditional tag of the page, post, custom post type you do not want to see loaded with a sidebar et voila!

Under extras.php you can see a class is added when the above mentioned array does not contain the conditional posts/pages/custom post types:

 // Add class if sidebar is active
 if (Setup\display_sidebar()) {
 $classes[] = 'sidebar-primary';

This as most pages either have a sidebar or aside or do not. Again, this is not relevant for footer or header sidebars or special sidebar areas.

Other Widgetized Areas

Other sidebars that are not content sidebars or asides loaded left or right of the main content are loaded in template files like they would normally be. They are normally added to the template parts where they should be loaded like the footer sidebar which is loaded from a template part using the following snippet in the base.php file:


This as they will not be part of the display_sidebars function as they are not content asides. In the footer template_part you will find:

<footer class="content-info">
 <div class="container">
 <?php dynamic_sidebar('sidebar-footer'); ?>

which loads the sidebar-footer. For most of our themes we have at least four footer widget areas or sidebars.


Well, that’s it. Now you know how Sage loads content sidebars or asides and other widgetized areas. You also know a bit more about the Sage theme wrapper used to keep things DRY. This to make sure we do not repeat ourselves. In the upcoming posts I will talk some more about the wrapper as well as creating template parts and general Sage theme development.

Sage Theme Deployment

When you work on a Sage theme with Trellis for your server stack  the final step tends to be the deployment of the theme itself. The Sage theme does get pulled onto the server using Trellis, but the dist folder isn’t.

This folder dist is excluded. This is done using the .gitignore. This is done as it is considered better practice not to have your compiled CSS and scripts on the repository. And those files wind up in that directory. Therefore it is left out.

Sage Theme Deployment Preparation

I assume you already provisioned your server running

ansible-playbook server.yml -e env=production

and did the general deployment using:

./ production

This already installed the LEMP server and WordPress and the them to begin with. See more information on Trellis setups here. I also assume you either installed online or imported the database with all the content.

Compiling all Assets for Production

Then, to have all the assets do a:

gulp --production

This to compile all files and remove source maps. This so you have all necessary CSS and JavaScript files without the unnecessary stuff for development of the theme.

Uploading Dist

Now to upload this dist folder as well I recommend using scp and the admin user. From the theme folder locally execute this command using the terminal:

scp -r dist/

This will copy over the dist folder with all its assets to your theme on the server. After that you will have no more 404s. The theme will load properly with all the CSS and JavaScript files present. Done!

NB Downside is that new deploys keep on removing the dist folder due to git setup.

Update: Better Alternative

Better alternative is to activate the build-before.yml hook file located in trellis/deploy-hooks.

# Placeholder `deploy_build_before` hook for building theme assets locally
# and then copying the files to the remote server
# Uncomment the lines below and replace `sage` with your theme folder:
- name: Run npm install
 command: npm install
 connection: local
 chdir: "{{ project.local_path }}/web/app/themes/sage"

- name: Run bower install
 command: bower install
 connection: local
 chdir: "{{ project.local_path }}/web/app/themes/sage"

- name: Run gulp
 command: gulp --production
 connection: local
 chdir: "{{ project.local_path }}/web/app/themes/sage"

- name: Copy project local files
 src: "{{ project.local_path }}/web/app/themes/sage/dist"
 dest: "{{ deploy_helper.new_release_path }}/web/app/themes/sage"
 group: no
 owner: no
 rsync_opts: --chmod=Du=rwx,--chmod=Dg=rx,--chmod=Do=rx,--chmod=Fu=rw,--chmod=Fg=r,--chmod=Fo=r