Adding a Coming Soon Page in WordPress

Before we launch a new WordPress project, often there comes a point where we need some form of a coming soon page. Usually when the domain is booked and ready, but the theme development is still in progress and the content is not finished yet.

Typically, clients want to start using their new email-address(es) right away, which means their clients can see the new domain and potentially visit it. So it’s nice to have some form of a landing page up instead of a blank page or even an error message.

Of course, there are plenty of Maintenance Mode plugins, from super simple to more complex ones that let you edit content and add special features like countdowns and what not.

But none of those really matched our workflow. So until today I was often just slapping together an HTML-page with the styles and everything inlined, and then copied this file to the root of the domain manually and removed it when the theme was ready. While this “worked”, it always felt wrong and kinda hacky. This week I had a little bit of time and thought I’ll see what I can come up with.

To fit our workflow perfectly, it would need to cover the following things:

  1. I’d like to edit this coming soon page from a template file
  2. No editing of content from the backend needed
  3. I want it to be version controlled
  4. I want it to be easily deployable

We usually have a git repository in place per theme (or plugin) we build, and we use grunt with rsync to deploy changes to the production site. Therefore it felt naturally to put this right into our theme, where we get the deployment and versioning “for free”.

Adding a maintenance.php template

To add our template, I created a new file maintenance.php inside my theme. It wold also work with a plain HTML file, but I went with PHP as this will make it easy to add some dynamic content, if needed.

Right now, the content of my maintenance.php file looks something like this:

<!doctype html>
<html lang="de">

<head>
	<meta charset="utf-8" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
	<title>Title – Coming Soon</title>
	
	<style>

		<!-- All styles inlined in here -->

	</style>
</head>

<body>

	<div class="logo">
		<!-- Logo as inline SVG in here -->
	</div>
	<div class="message">
		<address class="address">Client Name, Street Address 1, City</address>
		<h2 class="title">COMING SOON</h2>
	</div>
	
</body>

</html>

How to switch on our “Maintenance Mode”

As this is something we will only control ourselves, we will not necessarily need a UI for clients at the moment. To activate maintenance mode, I use a constant that we define in our wp-config.php file.

define( 'MAINTENANCE_MODE', true );

Loading our maintenance.php template

Now, to actually load the template when visitors visit the website, we make use of the template_include filter. This filter lets us control which template file is getting loaded by WordPress:

This filter hook is executed immediately before WordPress includes the predetermined template file. This can be used to override WordPress’s default template behavior.

WordPress.org Code Reference

In our case, we want all requests to be routed to our template, if the “MAINTENANCE_MODE” constant is set to true:

/**
 * Maintenance mode
 */
function haptiq_maintenance( $template ) {
	if ( defined( 'MAINTENANCE_MODE' ) && true === MAINTENANCE_MODE ) {
		$template = get_template_directory() . '/maintenance.php';
	}
	return $template;
}
add_filter( 'template_include', 'haptiq_maintenance', 10, 2 );

Put this into your themes’ functions.php, add a maintenance.php template at the root level of your theme, and switch MAINTENANCE_MODE on from your wp-config.php, and you should see your template for all requests.

Adding some sprinkles on top

After using this for about a week on a client site, I added some small-ish additions to the functions.php code.

First, I added a check for a URL-parameter ?preview which lets you circumvent the maintenance template and see the site. This is something I’ll probably only use when needed, but I find it’s a nice touch to be able to get a preview of the theme quickly, if needed.

Second, I added a check for logged-in users, so that you always see the site when you are logged in.

Here’s the full functions.php code including both those additions:

/**
 * Maintenance mode
 */
function haptiq_maintenance( $template ) {

	if ( isset( $_GET['preview'] ) ) {
		return $template;
	}

	if ( is_user_logged_in() ) {
		return $template;
	}

	if ( defined( 'MAINTENANCE_MODE' ) && true === MAINTENANCE_MODE ) {
		$template = get_template_directory() . '/maintenance.php';
	}
	return $template;
}
add_filter( 'template_include', 'haptiq_maintenance', 10, 2 );

Things to improve

This solution is not perfect and I’m sure there’s still lots to improve, but I like it so far! It lives in my theme, which means I can easily deploy changes to production, and have them committed to our shared code-base. Also, I can switch it on easily from the wp-config.php without touching any code or copying/removing files on the server.

Here are some ideas that I would like to improve upon:

  • Ability to use CSS from the theme. Right now, styling is completely separated from the theme and therefore there’s some repetition of styles. Would be nice to be able to use stuff like fonts from the theme, if they are already defined in there.
  • Make content more project-independent. Right now the text, title tags and logo etc. are all hard-coded in the template, so it’s still a bit of copy-pasting and replacing when using it in a new project. Maybe this could be made a bit more independent in the future so we can re-use it more easily.

What do you think? Do you use maintenance plugins? Other ways of adding a custom maintenance/coming-soon page? Ping me on twitter or send me and email. Would love to hear how others handle this.

Local Development Setup on macOS

For the better part of the last 10 years or so, I was using MAMP (or later MAMP Pro) for my local development workflow. During this time, my requirements changed quite a bit compared to when I started. This became apparent, when we were looking for ways to streamline and automate some of our maintenance workflows. While MAMP is a comparably “easy” solution to setup sites, which works quite well, it has some limits – especially when you want to automate and script things.

While researching, I stumbled across several different solutions, from the “do-everything-yourself and set up *everything* manually” to docker-based, to varying vagrant vagrants or Laravel Valet.

I never really took the time to finally look into all of them, and just kept everything as it was, still using MAMP. But last week, I was setting up my new MacBook Pro, and I thought there will never be a better time to give it a shot and see what I can optimize.

After reading through some of the docs and especially after reading this article by Jonathan Bossenger, I figured that while all of the solutions have their own pros and cons, Laravel Valet sounds like it fits my workflow the best.

Here’s what I ended up with, at least for now.

πŸ“ The Requirements

  • I have my sites stored in one place (~/Development/vhosts/...)
  • I don’t want to have to do all sorts of configurations for each site I install. Most of the sites I’m working with are basic WordPress installs and quite similar to each other
  • I want to be able to install and migrate (or just migrate if already installed) the current state of a live site, fast!
  • I want to be able to install a new site quickly without much setup, and this should be scriptable from the command line
  • I want to be able to install WordPress plugins and themes with the same easy install script
  • I want the ability to check out some of the plugins/themes from our git repos, instead of copying them from the live site
  • I want to be able to switch between php versions easily, bonus points if this could be defined per site, but I can live with a global switch
  • I want to use Mailhog to catch all mails from local installations

πŸ§ͺ The Ingredients

  • macOS Monterey 12.1 (on M1 Apple Silicon)
  • Homebrew
  • Composer
  • valet+ – a fork of Laravel Valet (needed a bit of a workaround to get it running, see below)
  • wp-cli
  • wp valet – custom cli command
  • Custom Migration & Installation Scripts
  • SSH access to the live servers
  • SSH access to the git repos

πŸ—„ The Directory Structure

Not that it really matters – you could structure your setup completely different than mine and it should work exactly the same – but for the sake of documentation, here’s how I structure my Development directories:

  • ~/Development
  • ~/Development/scripts
  • ~/Development/vhosts

scripts – contains all my dev scripts, like the ones to install and migrate live servers to local and stuff like that.

vhosts – contains the actual directories for each of the sites I’m working on

πŸ“¦ Installing Valet+

Laravel Valet is a minimalist development environment for macOS. It was initially developed by & for Laravel, but it’s perfectly suited for WordPress Development as well. Valet+ is a fork by weprovide, which adds some useful features, like XDebug support, easily switching between PHP versions or Mailhog.

I pretty much followed the installation instructions, which I copied here and added my comments in brackets:

  1. Install Homebrew or update to the latest version using brew update.
  2. Add the Homebrew PHP tap for Valet+ via brew tap henkrehorst/php.
  3. Install PHP 7.3 using Homebrew via brew install valet-php@7.3 --build-from-source.
  4. Link your PHP version using the brew link valet-php@7.3 --force command.

    ⚠️ Sometimes you need to restart all terminal windows for the link to take effect after install. Especially if you just installed a homebrew PHP version for the first time.
  5. Install Composer using Homebrew via brew install composer.
    (This was spitting out some errors the first time I ran it, after the second time it worked πŸ€·β€β™‚οΈ)
  6. Install Valet+ with Composer via composer global require weprovide/valet-plus.
  7. Add export PATH="$PATH:$HOME/.composer/vendor/bin" to ~/.zshrc
  8. Run the valet fix command. This will check for common issues preventing Valet+ from installing.
    (This showed quite a few errors about uninstalled PHP versions, which I ignored as I only installed v7.3 and will install others later)
  9. Run the valet install command. Optionally add --with-mariadb to use MariaDB instead of MySQL This will configure and install Valet+ and DnsMasq, and register Valet’s daemon to launch when your system starts.
    (Would be cool to be able to do it without the daemon and only launch when needed, but that’s ok for now)

    πŸ˜΅β€πŸ’« Now, at this point it was spitting out a whole red wall of errors to my command line. Read on below on how to get those fixed.
  10. Once Valet+ is installed, try pinging any *.test domain on your terminal using a command such as ping -c1 foobar.test. If Valet+ is installed correctly you should see this domain responding on 127.0.0.1. If not you might have to restart your system. Especially when coming from the Dinghy (docker) solution.

The big red wall of an error in step 9 nearly made me give up, but after reading them closer, they turned out solvable. At least as a workaround. Here’s how I got valet install to run properly.

πŸ‘©β€πŸ”§ Fixing valet install

1. APCU installation location not found

After reading through the errors closely and investigating some GitHub issues that sounded like a similar problem, I found out that PHP seems to search for a library called PCRE/PCRE2 (Perl Compatible Regular Expressions) in the wrong location. To make it work I had to add Symlinks like this. I added those for every PHP version I installed, even though I’m actually not sure if all of them use this library.

ln -s /opt/homebrew/include/pcre2.h /opt/homebrew/Cellar/valet-php@7.3/7.3.27_2/include/php/ext/pcre/pcre2.h

2. GeoIP not found

Now that the APCU thing was running through, the next wall of errors was caused by a GeoIP module. According to some comments on Github this is not really used anymore, and the easiest workaround seemed to be deactivating it for all PHP versions I use. I know, I know, not the prettiest thing to change those files directly, but it works for now and I hope this gets fixed in a future version.

To deactivate GeoIP, we need to set some parameters to false for each PHP version by changing this file:

nano ~/.composer/vendor/weprovide/valet-plus/cli/Valet/Pecl.php

In there, we need to set GeoIP to false, like this:

    self::GEOIP_EXTENSION => [
        '8.0' => false,
        '7.4' => false,
        '7.3' => false,
        '7.2' => false,
        '7.1' => false,
        '7.0' => false,
        'extension_type' => self::NORMAL_EXTENSION_TYPE,
        'brew_dependency' => 'geoip'
    ],

After those two changes, I ran the valet fix and valet install commands again, and they ran through without any errors:

valet fix && valet install

πŸ“£ Register my vhosts directory with valet

Sites can now be added either manually, or – and this is what I’m going to do – I can register my whole vhosts directory by using the park command. To register it, I need to navigate to the vhosts directory and run:

valet park

Now all directories inside of vhosts will automatically be accessible from a web browser at <directory name>.test (or another domain ending you chose, see next step).

🚩 Changing the domain ending

I like to have my local sites under <name>.localhost, instead of the default <name>.test that valet uses. Luckily it’s easy to change this by running the domain command:

valet domain localhost

πŸ‘©β€πŸ’» Installing the wp valet cli command

To quickly spin up a new site from the command line there’s even a command for WP-CLI. Install the wp valet command like this:

wp package install aaemnnosttv/wp-cli-valet-command:@stable

πŸͺ„ Installing a new site

Ok, now that everything is setup and running, all I have to do to create a new site is navigating to my vhosts directory and run the following command (replacing <name> with a name for the site):

wp valet new <name>

The command also lets me set a lot of parameters for the WP Install. For example, I could install WP version 5.8.3 like this. You can find all the options for the command here.

wp valet new <name> --version=5.8.3

This will do the following things:

  1. Create a new directory with the name provided
  2. Creating a new database named wp_<name>
  3. Download and install WordPress
  4. Create an SSL certificate
Et voilΓ : Creating a new local site only takes seconds.

πŸ’£ Removing a site

The following command removes a site completely, including all its files & the database:

wp valet destroy <name>

🐘 Changing PHP versions

If you want to let valet run with a different PHP version, you can do so with the use command, this will download and install (if not already installed) the specified version and restart valet to use this version:

valet use php@8.0

βœ‰οΈ Catching mails with Mailhog

I was using Mailhog before to intercept all mails going out from local installations. This is important to prevent local sites from sending emails, especially when working with client sites.

Luckily, Mailhog comes already preinstalled and configured with valet-plus, and the mails can be accessed by opening:

http://mailhog.localhost

πŸ‘ Wrapping up

As you can see, valet does a lot of cool things out of the box, and makes setting up local sites as easy as it can be for me. Apart from what I’ve shown here, valet should also make your sites accessible from your local network or even lets you share them publicly, which is awesome for testing on other devices. I still have a list of things to try and am still working on some scripting to completely automate the process of setting up and migrate live sites, but so far I really like how it works.

I guess the only thing that I’m missing, is that it exclusively runs Nginx, so I might still need another solution for when I really need something tested on Apache. But other than that it fits my workflow quite perfectly!

I wrote this guide mainly to document the process for my future self, when I have to do this again, but maybe it’s helpful for others as well.