Ga naar inhoud

WordPress Roots Stack

Table of Contents


Overview

The Roots stack is Lemone's preferred WordPress development framework, consisting of three tools:

Bedrock

Modern WordPress stack with improved folder structure, dependency management, and environment-based configuration.

Key features: - WordPress as a Composer dependency - Better folder structure (web/wp/ for WordPress core) - Environment-based configuration (.env files) - Dependency management via Composer - Enhanced security (WordPress not in web root)

Resources: roots.io/bedrock

Sage

Modern WordPress starter theme with front-end tooling powered by Bud.

Key features: - Modern JavaScript (ES6+) - Blade templating engine - Tailwind CSS (or Sass) - Hot module reloading - Laravel Mix / Bud build system

Resources: roots.io/sage

Trellis

Ansible-based WordPress LEMP stack for deployment and server management.

Key features: - Automated provisioning - Zero-downtime deployments - SSL certificates (Let's Encrypt) - Multiple environments (development, staging, production) - Security hardening

Resources: roots.io/trellis


Adding Composer Plugins

Installing WordPress Plugins via Composer

Step 1: Find the plugin on WPackagist

Visit wpackagist.org and search for your plugin.

Step 2: Navigate to site folder

cd site

Step 3: Install plugin

lando composer require wpackagist-plugin/plugin-name

Example:

# Install Yoast SEO
lando composer require wpackagist-plugin/wordpress-seo

# Install Contact Form 7
lando composer require wpackagist-plugin/contact-form-7

Step 4: Commit changes

git add composer.json composer.lock
git commit -m "feat(plugins): add Yoast SEO plugin"

Tip

Always commit both composer.json and composer.lock to ensure consistent plugin versions across environments.


Google Analytics

Adding Google Analytics (Universal Analytics)

Warning

Note: Universal Analytics (UA) is deprecated. Consider using Google Analytics 4 (GA4) instead. This guide covers the legacy UA method using Soil.

Step 1: Create GA Property

Create and copy your GA tracking code from Google Analytics.

Step 2: Add Soil Support

Edit site/web/app/themes/your-theme/app/setup.php:

add_theme_support('soil-google-analytics', 'UA-XXXXX-Y');

Replace UA-XXXXX-Y with your actual tracking ID.

Step 3: Commit changes

git add site/web/app/themes/your-theme/app/setup.php
git commit -m "feat(analytics): add Google Analytics tracking"

React Integration

Setting Up React in Sage

Use React components within your WordPress theme.

Step 1: Install React packages

Edit package.json and add:

{
  "devDependencies": {
    "@roots/bud": "6.12.3",
    "@roots/bud-sass": "6.12.3",
    "@roots/bud-tailwindcss": "6.12.3",
    "@roots/bud-react": "6.12.3",
    "@roots/sage": "6.12.3"
  },
  "dependencies": {
    "react": "18.2.0",
    "react-dom": "18.2.0"
  }
}

Warning

Version matching is critical: Ensure @roots/bud-react version matches @roots/bud and @roots/sage. Also ensure react and react-dom versions match.

Step 2: Install dependencies

yarn install

# Restart dev server
yarn dev

Step 3: Create render target

In your Blade template (e.g., app.blade.php), add a div with an ID:

<div id="helpbot"></div>

Step 4: Create React component

Create resources/scripts/components/helpbot/helpbot.jsx:

import { useState } from 'react';

const HelpBot = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount((count) => count + 1);
  };

  return (
    <>
      <button onClick={handleClick}>
        Clicked: {count}
      </button>
    </>
  );
};

export default HelpBot;

Step 5: Multiple component files (optional)

Create resources/scripts/components/helpbot/title.jsx:

const Title = ({ title }) => {
  return (
    <>
      <h1>{title}</h1>
    </>
  );
};

export default Title;

Import in helpbot.jsx:

import { useState } from 'react';
import Title from "./title";

const HelpBot = () => {
  return (
    <>
      <Title title="This is a title" />
      <button>Click me</button>
    </>
  );
};

export default HelpBot;

Step 6: Render component

In your main JavaScript file (app.js), render the component:

import { createRoot } from 'react-dom/client';
import HelpBot from './components/helpbot/helpbot';

const container = document.getElementById('helpbot');
if (container) {
  const root = createRoot(container);
  root.render(<HelpBot />);
}

Resources: - Roots Discourse: Sage 10 with React 18


SVG in React

Using SVGs as React Components

For projects using React (Radicle or Sage with React), you can import SVGs directly as components.

Warning

This guide assumes icons are stored in resources/images/icons. Adjust paths if your structure differs.

Step 1: Install @svgr/webpack

yarn add -D @svgr/webpack

Step 2: Configure bud.config.ts

Add to the end of the config function:

export default async (bud: BUD) => {
  // ... existing config

  bud.hooks.on(`build.module.rules.oneOf`, (rules = []) => {
    rules.unshift({
      test: /\.svg$/,
      use: [`@svgr/webpack`],
    });

    return rules;
  });

  bud.alias(`@icons`, bud.path(`resources/images/icons`));
}

Step 3: Update tsconfig.json

Add icon path alias in compilerOptions.paths:

{
  "compilerOptions": {
    "paths": {
      "@icons/*": ["images/icons/*"]
    }
  }
}

Step 4: Use SVGs in components

Import SVG as a component:

import ArrowIcon from "@icons/functional/arrow.svg";

Use in JSX:

<div className="w-4 h-4">
  <ArrowIcon />
</div>

Tip

Ensure your SVG files only have a viewBox attribute (no width or height). This allows you to control size via CSS.


Bedrock Multisite

Setting Up WordPress Multisite with Bedrock

Bedrock requires a URL fix for multisite network admin paths to work correctly.

Step 1: Install URL fixer

composer require roots/multisite-url-fixer

Or manually add must-use plugin:

Create site/web/app/mu-plugins/ms-url-fixer.php:

<?php
/**
 * Plugin Name: Multisite URL Fixer
 * Plugin URI: https://github.com/roots/bedrock/
 * Description: Fixes WordPress issues with home and site URL on multisite.
 * Version: 1.0.0
 * Author: Roots
 * License: MIT License
 */

namespace Roots\Bedrock;

if (!is_multisite()) {
    return;
}

class URLFixer {
    private static $instance = null;

    public static function instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function add_filters() {
        add_filter('option_home', array($this, 'fix_home_url'));
        add_filter('option_siteurl', array($this, 'fix_site_url'));
        add_filter('network_site_url', array($this, 'fix_network_site_url'), 10, 3);
    }

    public function fix_home_url($value) {
        if ('/wp' === substr($value, -3)) {
            $value = substr($value, 0, -3);
        }
        return $value;
    }

    public function fix_site_url($value) {
        if ('/wp' !== substr($value, -3)) {
            $value .= '/wp';
        }
        return $value;
    }

    public function fix_network_site_url($url, $path, $scheme) {
        $path = ltrim($path, '/');
        $url = substr($url, 0, strlen($url) - strlen($path));

        if ('wp/' !== substr($url, -3)) {
            $url .= 'wp/';
        }

        return $url . $path;
    }
}

URLFixer::instance()->add_filters();

Deploying Multisite to Server

Step 1: Disable multisite ENV vars

In application.php, comment out multisite configuration:

/**
 * Multi site
 */
#Config::define('WP_ALLOW_MULTISITE', true);
#Config::define('MULTISITE', true);
#Config::define('SUBDOMAIN_INSTALL', false);
#Config::define('DOMAIN_CURRENT_SITE', env('DOMAIN_CURRENT_SITE'));
#Config::define('PATH_CURRENT_SITE', env('PATH_CURRENT_SITE') ?: '/');
#Config::define('SITE_ID_CURRENT_SITE', env('SITE_ID_CURRENT_SITE') ?: 1);
#Config::define('BLOG_ID_CURRENT_SITE', env('BLOG_ID_CURRENT_SITE') ?: 1);

Step 2: Deploy and install WordPress

Install WordPress normally via browser.

Step 3: Enable WP_ALLOW_MULTISITE

Uncomment only this line:

Config::define('WP_ALLOW_MULTISITE', true);

Step 4: Enable network in WordPress admin

  1. Log in to WordPress admin
  2. Go to ToolsNetwork Setup
  3. Choose subdomains or subdirectories
  4. Click Install

Step 5: Enable remaining multisite settings

Uncomment all multisite ENV vars in application.php.


SMTP via Postmark

Configuring Transactional Email with Postmark

Step 1: Create sender in Postmark

  1. Log in to Postmark
  2. Go to Sender SignaturesDomain → Enter domain name
  3. Go to ServersCreate → Enter client name

Step 2: Configure DNS

Add two DNS records (provided by Postmark): 1. DKIM record (DomainKey) 2. Return-Path record

Check the Sender Signatures tab in Postmark for the exact records to add.

Example DNS records:

Postmark DNS Setup 1

Postmark DNS Setup 2

Step 3: Install Postmark plugin

lando composer require wpackagist-plugin/postmark-approved-wordpress-plugin

Step 4: Configure plugin

  1. Activate plugin in WordPress admin
  2. Go to SettingsPostmark
  3. Enter your Postmark API key (Server API Token)
  4. Configure sender email and name

Note

Don't forget to install the Postmark plugin before deploying.


Updating Trellis

Upgrading Trellis to Newer Versions

Step 1: Create upgrade branch

git checkout -b upgrade

Step 2: Clone latest Trellis

git clone --depth=1 https://github.com/roots/trellis.git upgrade

Step 3: Remove .git folder

rm -rf upgrade/.git

Step 4: Copy files to your Trellis folder

rsync -ah --progress upgrade/ ./

Step 5: Remove upgrade folder

rm -rf upgrade

Step 6: Review changes and test

git diff

Review all changes carefully, especially if you have custom configurations.

Step 7: Commit upgrade

git add .
git commit -m "chore(trellis): upgrade to latest version"

Server Monitoring

Setup Prometheus Monitoring with Trellis

Step 1: Add to requirements.yml

Edit trellis/requirements.yml (or galaxy.yml):

- name: node-exporter
  src: cloudalchemy.node-exporter
  version: 2.0.0

Step 2: Add to server.yml

Edit trellis/server.yml:

- { role: node-exporter, tags: [node-exporter] }

Step 3: Update security.yml

Edit trellis/group_vars/all/security.yml and add to ferm_input_list:

ferm_input_list:
  - type: dport_accept
    dport: [http, https]
    filename: nginx_accept
  - type: dport_accept
    dport: [ssh]
    saddr: '{{ ip_whitelist }}'
  - type: dport_limit
    dport: [ssh]
    seconds: 300
    hits: 20
  # Add this block:
  - type: dport_accept
    dport: [9100]
    saddr: [128.199.44.89]  # Your monitoring server IP
    accept_any: false

Step 4: Install requirements

ansible-galaxy install -r requirements.yml

Step 5: Provision server

ansible-playbook server.yml -e env=production --tags=node-exporter,ferm

Troubleshooting

Error: objc[95218]: +[__NSPlaceholderDate initialize] may have been in progress

Fix (macOS only):

export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

Then run your playbook again.

Error: Make sure the required command to extract the file is installed

Install GNU tar:

brew install gnu-tar

Resources: - How To Install Prometheus on Ubuntu 16.04


See Also