How to replace the WordPress frontend with Twig, FastRoute and Dice

I consider this guide an experiment, a test to see if its possible to replace the Wordpress frontend entirely. Getting rid of Wordpress frontend means getting rid of overhead like database requests, bad plugin code. Instead we get performant, clean templates and have more control over the frontend. This is a rather extreme step with downsides. You loose all the frontend logic, plugins, search result pages, category pages, archive and so on – you loose flexibility, have to rebuild all of the thinngs just mentioned from scratch. Still this might be worth it, if you love Twig, and want to bring it's benefits into the Wordpress cosmos. In this article I outline what you need to get started.

Over the last years I have worked with a couple of modern CMS platforms and technologies like Adobe CQ5, Craft CMS, Craft Commerce, Zend Expressive, Laravel and so on.

Working with WordPress again is painful at times, specifically looking at its template engine, its database design (with all sorts of meta tables, taxonomies and multiple data types like orders, posts, pages, invoices into one single wp_posts table) and the way hooks, filters and actions make it virtually impossible to know which, how much PHP code and its what order it is executed at a single request.

That said, I perfectly understand that things are like this for a good reason. This allows a backwards compatibility which allows the use of wide range of plugins. Improving the database structure would render most plugins useless. But speaking as a developer, I want clean code and aI want a fast website and I don’t care for backwards compatibility.

In order to have a modern frontend, let’s write a WordPress Plugin which uses Dice dependency injector, FastRoute to handle routes, Symfony’s Twig Templating Engine for our views and a Symphony Console Command which is in charge of exporting posts and pages from the DB into a compact PHP Array every now and then.

Step 1: The WordPress Frontend Plugin

Grab a copy of this basic plugin here. Add it to your WordPress installation. Done. There’s no admin page, or settings page included. You simply use the new frontend by pointing your document root to the plugins’ public folder.

If you want to test it, just open the URL directly.

Download WordPress Twig Frontend (7.4 MB)

Step 2: The Cronjob updating our PHP-Array

Now we can create a cronjob with crontab -e and add the following line

*/15 * * * * cd /var/www/html/wordpres/wp-content/plugins/wordpress-twig-frontend && php bin/posts-to-json.php

which will cause the export to run every 15 minutes.

The decision to use a cronjob over native WordPress action hooks, is a very conscious one. Although it would be easy to trigger an update of the PHP Array whenever a post is created or an existing one is updated, this approach would require a lot of extra code. Think of what WooCommerce does, when someone purchases a specific product in your online shop. It creates an order in your wp_posts table, it updates the product (adding the cart quantity to the total of items sold in the wp_postmeta table, creates invoices, and more. Because there is so much going on in the database, it would be necessary to define and validate a handful of rules per post_type, to decide wether an update is relevant to trigger a PHP Array rebuild or not. More importantly we would need to make sure and avoid that PHP Array is rebuilt several times per second.

This is why, it seems to be better to define how long the data managed in the backend should be considered valid (in this example 15 minutes) and then rebuild the PHP Array.

This has disadvantages since I cannot use any other frontend related plugins anymore. But it does give me clean templates and a fast frontend without having to load any WordPress Core files nor templates nor do I need to query the database. This creates an array structure like seen below, including post fields, meta fields, and so on.

... 31 => [ 'ancestors' => [], 'comment_status' => 'open', 'filter' => 'raw', 'guid' => '', 'id' => 31, 'meta_encloseme' => '1', 'meta_pingme' => '1', 'meta_yoast_wpseo_primary_category' => '1', 'page_template' => '', 'post_author' => '1', 'post_category' => [ 0 => 1, ], 'post_content' => '<div class="post-content">Hello World.</div>', 'post_date' => '2020-02-03 09:52:36', 'post_excerpt' => '', 'post_exported_date' => '2020-11-25 15:28:24', 'post_exported_time' => 1606318104, 'post_mime_type' => '', 'post_modified' => '2020-02-07 14:52:15', 'post_name' => '31', 'post_parent' => 0, 'post_permalink' => 'http://localhost:8001/2020/02/03/31/', 'post_title' => 'Hello World', 'search_content' => '', 'search_full' => 'Hello World', 'search_title' => 'Hello World', 'type' => 'post', ], ...

Step 3: Do your actual programming

By default, the plugin comes with only a few Controllers, HomeController.php, SearchController.php, PostController.php, which basically led the JSON/PHP Array created by the CLI command, and passes it to Twig. Your actual Twig templates go in storage/resources/views.

Step 4: Happy templating

Let’s finally take a look at one of these Twig templates. This blogs’ homepage article listing.

{% for item in post %} <article class="post{{ }}" data-id="{{ }}"> <a href="/{{ item.post_name }}/" title="{{ item.post_title }}"> <time datetime="{{ item.post_date }}">{{ item.post_date|date('F jS, Y') }}</time> <h2>{{ item.post_title }}</h2> {{ item.post_excerpt|raw }} <p class="read-more">Read more </p> </a> </article> {% endfor %}

Sweet. Isn’t it? As you can see we access, the PHP array keys from the array example above. Besides the beauty of the Twig syntax, it also brings performance benefits. Twig templates are pre-rendered into a more machine-friendly syntax and cached for subsequent requests.


This is a lot of work, it’s not suitable for everyone, or every type of website. But, it shows that it is possible to combine modern technologies with WordPress as a backend – and this way have the most performant, modern, Twig-based frontend possible. Without any overhead from DB requests, without loading any WordPress file at all, when a request is made.