Craig Simpson

Web developer from Moray, Scotland

My Process: the WordPress install and site structure

6th November 2023

No Comments

Despite the blog radio-silence, I’m still here building WordPress themes and plugins as part of my everyday work. And despite all of the recent ups and downs with the block editor, WordPress remains one of the most user-friendly ways to build a bespoke website that’s easy to update and grow.

In this series of blog posts, my aim is to walk you through the process I follow when creating a WordPress website from scratch, from the WordPress install and site structure, through to theme development, selecting and using plugins, adding content, testing and launch.

Using WPStarter

Creating a new WordPress project starts with a single file, composer.json.

Since being introduced to WPStarter in 2016, I think I’ve used it on almost every project I’ve built. If you know of Roots Bedrock, this is a similar, less-opinionated approach to bootstrapping your entire WordPress installation with Composer.

There are a number of important benefits to this approach in general:

  • It allows the whole website to be bootstrapped from a single file, making it easy for you or another developer to quickly create a copy of the site in a local development environment or staging environment.
  • Rather than storing database credentials and other sensitive information inside wp-config.php, it uses environment variables that can be easily tailored to local, staging or production environments.
  • If you use Composer to manage dependencies in your plugin or theme, they’ll be automatically auto-loaded from within the project’s vendor folder, rather than needing to be installed on a per-plugin or per-theme basis.
  • You can maintain granular control of plugin versions and updates by locking down the WordPress installation, and only allowing out updates via the command line using composer update.
  • You can easily include WordPress plugins or themes that you maintain in private GitHub repositories, such as custom client code, premium plugins, or internal tools, and keep them up to date without needing to build in self-update scripts like GitHub Updater.
  • You can easily include WordPress plugins from the public WordPress plugin repository, with the help of the WordPress Packagist website, which mirrors the WordPress plugin and theme directories as a Composer repository.
  • You can create a “base install” that loads all of your commonly used plugins from a single composer.json file.

Typical Structure

craigsimpson-scot/
├─ httpdocs/
│  ├─ app/
│  │  ├─ plugins/
│  │  ├─ themes/
│  │  ├─ uploads/
│  │  ├─ vendor/
│  ├─ wp/
│  │  ├─ wp-admin/
│  │  ├─ wp-config/
│  │  ├─ wp-includes/
│  │  ├─ // ... other WP files
│  ├─ index.php
│  ├─ wp-config.php
├─ .env
├─ .gitignore
├─ composer.json
├─ composer.lock
├─ wp-cli.yml

When I’m building with WPStarter, a typical project follows the folder structure above.

There are a few interesting points to note:

  • All our credentials are stored in the .env file, located outside of the web root.
  • The WordPress core files are in their own folder, at httpdocs/wp/
  • All of the custom code, themes, plugins, uploads and vendor files are located in the httpdocs/app directory.
  • This structure is entirely automatic, and based on the configuration in our composer.json.

Starting A New Project

A from-scratch website project will comprise two distinct version-controlled repositories:

  • One containing our website bootstrap file.
  • One containing our custom WordPress theme.

When starting a new project, I’ll first create an empty repository for the theme. I like to use GitHub for version control, and so within the GitHub interface I’ll usually add the first file to my theme, another composer.json

This one isn’t complex, its only purpose at this point is to allow our theme to be loaded by our WPStarter bootstrap.

A basic WordPress theme composer.json file might look as follows:

1{
2 "name": "craigsimps/pocket",
3 "type": "wordpress-theme",
4 "description": "A WordPress theme custom built for my personal website, craigsimpson.scot.",
5 "license": "MIT",
6 "authors": [
7 {
8 "name": "Craig Simpson",
9 "role": "Developer",
10 "homepage": "https://craigsimpson.scot",
11 "email": "craig@craigsimpson.scot"
12 }
13 ]
14}

With that in place, I move on to creating a new local folder for my project, at ~/Sites/craigsimpson-scot and I’ll create a composer.json file at the root. Details of my local web development environment and the software and tools I use are available in another blog post in this series.

This root-level composer.json file is where we bootstrap the whole website using WPStarter.

The composer.json for this site is shown below:

1{
2 "name": "craigsimps/craigsimpson-scot",
3 "description": "Bootstrap file for the project name website, developed by Craig Simpson.",
4 "type": "project",
5 "repositories": [
6 {
7 "type": "composer",
8 "url": "https://wpackagist.org"
9 },
10 {
11 "type": "composer",
12 "url": "https://satis.craigsimpson.scot/satispress/"
13 },
14 {
15 "type": "vcs",
16 "url": "git@github.com:craigsimps/pocket.git"
17 }
18 ],
19 "require": {
20 "craigsimps/pocket": "dev-main",
21 "satispress/advanced-custom-fields-pro": "~6.1.4",
22 "wecodemore/wpstarter": "~2.0",
23 "wpackagist-plugin/autodescription": "~4.2.8",
24 "wpackagist-plugin/resizable-editor-sidebar": "~1.0.1"
25 },
26 "require-dev": {
27 "wpackagist-plugin/regenerate-thumbnails": "~3.1.5"
28 },
29 "config": {
30 "vendor-dir": "httpdocs/app/vendor",
31 "optimize-autoloader": true,
32 "allow-plugins": {
33 "composer/installers": true,
34 "johnpbloch/wordpress-core-installer": true
35 }
36 },
37 "scripts": {
38 "post-install-cmd": "WCM\\WPStarter\\Setup::run",
39 "post-update-cmd": "WCM\\WPStarter\\Setup::run"
40 },
41 "extra": {
42 "wordpress-install-dir": "httpdocs/wp",
43 "wordpress-content-dir": "httpdocs/app",
44 "installer-paths": {
45 "httpdocs/app/plugins/{$name}": [
46 "type:wordpress-plugin"
47 ],
48 "httpdocs/app/mu-plugins/{$name}": [
49 "type:wordpress-muplugin"
50 ],
51 "httpdocs/app/themes/{$name}": [
52 "type:wordpress-theme"
53 ]
54 }
55 }
56}

Within the repositories block (lines 5-17), we add the WordPress Packagist repository, details of our custom theme repository, and details of any other private repositories that we’ll be drawing from. In my case, I use SatisPress to manage access to the premium plugins I like to use, so I’ve added it as a repository.

Within the require block (lines 19-25), we define all of the dependencies of our project – our custom theme, and then any other WordPress plugins we’re using.

For this site, the list of dependencies is short:

  • Our custom theme, which is called Pocket
  • The Advanced Custom Fields Pro plugin, loading from my SatisPress installation.
  • The SEO Framework plugin, loading from WordPress Packagist
  • Regenerate Thumbnails, loading from WordPress Packagist
  • Resizable Editor Sidebar, loading from WordPress Packagist

You’ll note that the Regenerate Thumbnails plugin is listed in a separate require-dev block (lines 26-28). This is because packages listed within require-dev are ones that aren’t necessary for the project to work, and therefore shouldn’t be included in the production version of the project.

While we’re working on the website locally, or on a staging server, we may need to update the image sizes and regenerate thumbnails. But once the website is complete and moved to production, there will be no such need – at that point, the image sizes are fixed and won’t be altered.

And so, when installing the site on production, I would use the composer install --no-dev command, to instruct Composer not to install the Regenerate Thumbnails plugin, or any other plugins included within the require-dev block.

Our root-level composer.json file also contains some additional configuration for WPStarter, setting installer paths, the vendor directory path, and auto-running the WPStarter setup process when the composer install (or composer update) completes. These are all customisable, but for the most part, I leave them set to their defaults.

After Installation

Before I can work on the site locally there’s one more step I need to complete. Using Laravel Valet locally, its default behaviour is to load from my root folder, rather than the httpdocs folder, so I have to move inside it in the command line, and then use valet link to make sure the local website loads correctly.

In this case, my command inside the httpdocs folder is valet link craigsimpson-scot.localhost.

When this is done I can access the site in my browser at craigsimpson-scot.localhost and complete the WordPress installation process.

One important note

Structuring your WordPress installation this way has many benefits, as I’ve outlined, but using Composer to bootstrap your entire WordPress website is not “the WordPress way”, and often this approach won’t be possible if you’re working with certain premium WordPress hosting platforms.

For the sites I build, they’re either hosted by me, or within an environment that I’m in control of, so this non-standard structure isn’t a problem, but I understand not everyone has this flexibility. Your mileage may vary.

Leave a Reply

Your email address will not be published. Required fields are marked *