In this article we will talk about (1) custom themes built from scratch, (2) custom themes built upon a starter theme, (3) customization of commercial themes, and (4) themes built using a builder.
(1) Custom themes built from scratch (Creating The Parent Theme)
As the name applies these themes are created by coding all necessary files as the WordPress team (WordPress.org) require. Themes cannot include plugins, cannot require plugins, should not use any pseudo custom post types (CPT) and much more, if we want to submit it on WordPress.org.
This article is about creating a custom theme for a client, NOT for WordPress.org. This means we will make use of Custom Post Types (CPT). WordPress already has a lot of different Post Types as a default, these are always included in the WordPress installation and are stored in the wp_posts table. We would like to extend the default Post Types and create our own Custom Post Types.
How does a page like that even look?
Well… you are already on a page like that. This page uses CPT and Custom taxonomies (categories).
In order to have custom pages a lot of you folks use plugins, commercial themes and builders. If it is ok or not it all depends on your needs and budget. Here we talk about how to achieve the same page without the use of any builders, plugins or commercial themes in a way where the page will be more structured, scalable and robust with a few nice automated features and a clean admin.
Let’s see how a custom theme works and how can we make it flexible and highly customizable (we will try to mimic the builder experience you all love and go beyond). I will try to not post a bunch of code to make it harder than it already is for you to understand, just to make sure you can keep up with me. (for the sake of all readers…)
We will go through some examples, and I will explain why is it more beneficial for us.
When it comes to custom pages, I like to divide them in sections, and for every section I create a custom post type and/or a shortcode. Working with CPT is just a way to automate things rather than just creating a block to display something pretty as most of the builders do.
The first thing you will notice in the admin of a custom page that you no longer have those annoying plugin notifications everywhere, this is because you don’t really have any. Most of the page functionality is handled by the CPT you see in the admin menu. (History, Sliders, About, Services, etc). These are custom options for different sections in the page. Each of them behaves similar to default posts or pages that WordPress has as default.
These CPT can never break, you can make any update without worrying that something will stop working. (In worst scenario, you will need to refresh the permalinks). When it comes to plugins, the user can always extend the page functionality by installing any plugin or builder on top of the custom page and combine the CPT with it if he wants to (if it was developed that way off course), or just ask the developer to add the new features without the use of any plugin.
These pages have only the functionality you need and you use 100%, nothing more. (I’m sure you know how annoying it is when you install a theme and has a lot of options you don’t need and you never use, never to say all those plugin notifications present everywhere). This is a clean page with a clean admin.
Let’s see the plugins area.
We can see there are only 3 plugins installed. (all are optional) It is easy for us the developers to know right away any issue where it comes from. The problem with plugins is that most them will load scripts everywhere on the page even if we don’t use that plugin in every pages/posts.
This is normal, it doesn’t know where it will be used so it’s loaded everywhere. Let’s take the Contact form 7 plugin as example. After you install it you will notice all the pages will load some extra CSS and JS files.
We don’t want that on all the pages, so we need to take them out and load them only on pages where we actually use them. There are 2 ways doing so: (1) Hardcoding the exact location where we use the plugin (shortcode) or (2) registering/deregistering the assets depending on the shortcode.
There are a lot of ways to do this in code, let’s see a few examples:
Using constants and/or hooks:
// Disable CF7 assets
define( 'WPCF7_LOAD_JS', false );
define( 'WPCF7_LOAD_CSS', false );
// OR
add_filter( 'wpcf7_load_css', '__return_false' ); // Disable CF7 CSS
add_filter( 'wpcf7_load_js', '__return_false' ); // Disable CF7 Javascript
remove_action( 'wp_enqueue_scripts','wpcf7_recaptcha_enqueue_scripts', 20 );
/**
* Register by shortcode
*/
function weszty_web_asset_logic_cf7 ( $output, $shortcode ) {
if ( 'contact-form-7' == $shortcode ) {
wpcf7_recaptcha_enqueue_scripts();
wpcf7_enqueue_scripts();
wpcf7_enqueue_styles();
}
return $output;
}
add_filter( 'pre_do_shortcode_tag', 'weszty_web_asset_logic_cf7', 10, 2 );
OR:
/**
* Cut the fat by shortcode
*/
function weszty_web_asset_logic_cf7() {
$load_scripts = false;
if( is_singular() ) {
$post = get_post();
if( has_shortcode($post->post_content, 'contact-form-7') ) {
$load_scripts = true;
wp_enqueue_style( 'contact-form-7' );
wp_enqueue_script( 'contact-form-7' );
wp_enqueue_script( 'google-recaptcha' );
wp_enqueue_script( 'wpcf7-recaptcha' );
}
}
if( ! $load_scripts ) {
wp_dequeue_style( 'contact-form-7' );
wp_dequeue_script( 'contact-form-7' );
wp_dequeue_script( 'google-recaptcha' );
wp_dequeue_script( 'wpcf7-recaptcha' );
}
}
add_action( 'wp_enqueue_scripts', 'weszty_web_asset_logic_cf7', 99 );
OR:
/**
* Register scripts by shortcode
*/
function weszty_web_asset_logic_cf7($posts) {
if ( empty($posts) || is_admin() ) return $posts;
foreach ( $posts as $post ) {
if( has_shortcode($post->post_content, 'contact-form-7') ) {
wp_enqueue_style( 'contact-form-7' );
wp_enqueue_script( 'contact-form-7' );
wp_enqueue_script( 'google-recaptcha' );
wp_enqueue_script( 'wpcf7-recaptcha' );
break;
}
}
return $posts;
}
add_action( 'the_posts', 'weszty_web_asset_logic_cf7' );
These approaches are elegant but not really efficient since most of the themes out there have a large content thanks to builders. This function will search for shortcodes and enqueue the right scripts based on what we found.
The advantage of this method is that you can include a script or style in the head section of the page. This also has its disadvantages, searching for shortcodes in the content of every post is quite inefficient especially when it’s inside of builder pages mixed up with the builder blocks. (shortcodes)
If we don’t use builders and only our custom shortcodes to create the custom pages it is much faster because it will have 4-5 shortcodes only in the content. Nothing more.
Both of these solutions have their own advantages and disadvantages. I personally prefer the former since it is faster and more intuitive.
So how can we make it efficient for those kind of pages? Simple, we enqueue them by page.
add_filter( 'wpcf7_load_css', '__return_false' ); // Disable CF7 CSS
add_filter( 'wpcf7_load_js', '__return_false' ); // Disable CF7 Javascript
remove_action( 'wp_enqueue_scripts','wpcf7_recaptcha_enqueue_scripts', 20 );
/**
* Cut the fat by shortcode
*/
function weszty_web_asset_logic_cf7 () {
if ( is_page('contact') ) {
if ( function_exists( 'wpcf7_recaptcha_enqueue_scripts' ) ) wpcf7_recaptcha_enqueue_scripts();
if ( function_exists( 'wpcf7_enqueue_scripts' ) ) wpcf7_enqueue_scripts();
if ( function_exists( 'wpcf7_enqueue_styles' ) ) wpcf7_enqueue_styles();
}
}
add_filter( 'wp_enqueue_scripts', 'weszty_web_asset_logic_cf7', 10, 2 );
As we can see here we remove all the Contact Form 7 scripts from everywhere with the exception of the contact page. (We can also reference the page via the page ID not just the slug.)
What we know so far is that a lot of plugins will influence the page load.(I hope is more clear now, more plugins more scripts = long page load). If you are not a technical person the only thing you can do is to install lite plugins that do only one job, nothing more. Most of the plugins have way too many complex functionality that can lead to more or bigger scripts added in the page. There are good plugins out there, you just have to make more research and at least check the ratings with its comments.
There are plugins out there that do this optimization for you, like caching plugins or plugin addons, but why bother decreasing your site security + increased load time with those when you can do it with a few lines of code right?
PS: CF7 is a really popular and well written plugin compared to others out there, but even this plugin is far from perfect (please not these tests may not be valid for future versions) .
Let’s see a few issues here, so we can understand better why a bunch of plugins may cause some problems.
Starting with the plugin query:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID
FROM wp_posts
WHERE 1=1
AND wp_posts.post_type = 'wpcf7_contact_form'
AND ((wp_posts.post_status <> 'trash'
AND wp_posts.post_status <> 'auto-draft'))
ORDER BY wp_posts.ID ASC
LIMIT 0, 20
First of all, SQL_CALC_FOUND_ROWS is unnecessary because we don’t use it, I will not get into small details, this will be explained in another post on how can we optimize slow queries like this. Second of all, this query runs whether we use it or not on the current page, and it takes around 0.0009 seconds which is 3x slower than what it should take.
This can be solved by adding the following argument to the WP_Query:
no_found_rows => true // remove calculation of rows
Other issues are that Contact Form 7 relies on $_SERVER[‘SERVER_NAME‘] and $_SERVER[‘SERVER_PORT‘], which pass static values subject to change over time during routine platform maintenance.
There are solutions to these problems, we can solve this by adding the following code in wp-config.php :
$_SERVER['SERVER_NAME'] = $_SERVER['HTTP_HOST'];
if (isset($_ENV['PANTHEON_ENVIRONMENT'])) {
if (isset($_SERVER['HTTP_USER_AGENT_HTTPS']) && $_SERVER['HTTP_USER_AGENT_HTTPS'] === 'ON') {
$_SERVER['SERVER_PORT'] = 443;
}
else {
$_SERVER['SERVER_PORT'] = 80;
}
}
Going further, in order to attach or upload files, local file attachments set in the admin panel cannot come from the uploads folder. Therefore, you must set attachments to a temporary folder.
You can do that like this:
define( 'WPCF7_UPLOADS_TMP_DIR', WP_CONTENT_DIR . '/uploads/wpcf7_uploads' );
Please not that the temporary folder needs to reside in a folder that can be accessed by DEV Tests, Live tests, and whatever Multidev you are using.
At this time, this alone does not solve the issue, it has been submitted by the community and is being worked on here.
The temporary workaround is to comment out this code in your contact-form-7/includes/mail.php file:
# Comment out the following code:
if ( ! wpcf7_is_file_path_in_content_dir( $path ) ) {
if ( WP_DEBUG ) {
trigger_error(
sprintf(
/* translators: %s: Attachment file path. */
__( 'Failed to attach a file. %s is not in the allowed directory.', 'contact-form-7' ),
$path
),
E_USER_NOTICE
);
}
return false;
}
Sorry, I realized just now that I got caried away and went a bit to deep here on the issues. This article is not about how bad is CF7, even if this plugin adds dependencies using PHP Composer and provides a .gitignore which prevents dependencies from being picked up by git, this will lead to problematic deployments as not all the code moves forward to Test and Production.
We could solve this by removing the .gitignore files from the constant-contact-forms and vendor/psr/log directories…
Okay, okay, this was the last one, Jesus… chill already.
Going back on track, we can see all unused bites from Scripts and CSS files in a page in devtools Coverage. (Depending on the number of plugins and builders used this can be huge, this is why we try using more code and less plugins).
We must maintain the unused scripts as low as possible, same goes for the Doom. The Doom can increase drastically by using builders since they create a lot of nested elements in the page. (A lot of builders struggle to achieve a small Doom).
There are differences on how many nested elements a builder creates, I will not cover which is the best builder out there. From my perspective none are good enough, some of them are acceptable, like Gutenberg (WordPress native builder).
What we covered so far? We have full control over the content, shortcodes, scripts, queries, hooks, post types, and many more. Let’s make a small summary here of the advantages using custom themes:
(1) Fewer steps to process for rendering data
(2) A small amount of data is saved in the database (just a few shortcodes from the editor)
(3) The data that is saved does not include the design
(4) The data can be rendered inside any builder
(5) The data contains only clean text
(6) Full control over the whole process, sections, scripts, queries, etc
(7) No plugin dependency (no plugins, all plugins are optional, the theme will not break)
Let’s take a small example how these points above benefit us when we want to create a more complex functionality using the WordPress API.
Let’s say I would like to create a search like Google with suggestions. (You have a sidebar on the right that contains a search, write something in it that this page has, idk, “security” for example).
The search is making a small API request to get some content related to your needs. I will not be able to optimize and extend that search feature if the page used builders because the data would be messy and full of shortcodes that only particular builder understands (depends on the builder).
Even if I execute and render the content that way, we still have a problem with the size of the request. Let me show you:
Type in your current WordPress url after the domain:
/wp-json/wp/v2/posts/
it should look like this (mine is private for security reasons):
https://wesztyweb.com/wp-json/wp/v2/posts/
It will return a huge chunk of data with all the information about posts, we don’t want all of that in our search, so we need only the title, description and link in most cases, or just the title in this case. Because we have full control and clean data, I will create a new API Endpoint with just that, it looks like this:
[
{
"label": "WordPress A Complete Security Guide"
},
{
"label": "Security Add “noopener” and “noreferrer”"
},
{
"label": "All you need to know about WordPress Themes"
}
]
This will help me keep the request as small as possible so you can get the suggestions quick. By using builders this would be really messy and huge because the builder does not separate the data from the design/markup. (If we don’t add the builder content we good).
Having clean data is beneficial for us in case we want to do cool stuff like this, that also means you are able to combine WordPress with modern technologies like React/Vue/Angular/Svelte/Solid and many more out there using custom WordPress APIs.
Optimization is a huge subject, I will not cover it in this article, I will do a separate one just for that, this article is long enough already. I think you got the main idea why custom pages help in optimizations.
I still have a lot to say about these custom pages (we didn’t talked yet about custom WooCommerce pages), complex APIs, more optimization, security, hybrid templates, best practices, tips & tricks, and much more.
This is just the top of the iceberg that I covered here, but I hope you learned something from it, of what WordPress is capable of in the right hands.
I will try to shorten the other topics a bit, and point out only the essentials I consider important to know.
(2) Custom themes built upon a starter theme (Creating The Parent Theme)
Starter themes are a set of tools to ease your development. These themes have a unique file structure, they are built with modern PHP practices in mind and come with the most commonly used templates in a WordPress themes.
There are many starter themes out there and all of them have their own out-of-the-box includes.
A popular starter theme is Underscores, you will find a lot of starter themes based on this theme. There are more out there like Sage, Air Light, WP Rig, Beans and many more.
Starter themes are different from a parent theme or a theme framework. A starter theme is a blank theme with a minimum design, and a basic or absolutely no layout.
You are not supposed to use them as it is, these are used to create the parent theme. (these are boilerplates)
Everything we discussed in (1) Custom themes built from scratch apply to this way of development.
(3) Customization of commercial themes (Child theme)
These themes are fully styled WordPress themes designed to be installed and used on live websites right away. You can find a lot of themes like these on markets like themeforest, evato, colorlib etc.
Theme frameworks provide a lot of hooks and filters for usage in child theme development.
The majority of clients and companies use these themes for their services, it is a faster process. The
customization is done with builders, plugins or custom code that is stored in the Child Theme.
Pros
Cons
1) Delivered fast
2) Affordable prices for everybody
3) Easy to customize
4) Large community (easy to find help)
1) Easy to break (easy to mess things up)
2) Hard to find the right theme (speed issue, security issue, customization issue, updates issue)
3) A lot of options, templates the most of you will not use
(4) Themes built using a builder (Child theme)
This is similar to the (3) but I say it deserves his place to shine. There are a lot of powerful builders out there like : Elementor, WP Bakery (Visual Composer), Oxygen Builder, Beaver Builder, Bricks Builder etc.
These builders are powerful tools that are capable creating a page from start to finish without you knowing a single line of code. All of these builders are a part of the plugin category, it can be installed in any WP theme. These are used by many companies and clients.
Pros
Cons
1) Delivered fast
2) Affordable prices for everybody
3) Easy to customize
4) Large community (easy to find help)
1) Large Doom (a lot of nested elements)
2) Easy to break (easy to mess things up)
3) No version control
4) No control over scripts with limited/no control over queries
Final thoughts
All of these can be the right choice depending on your business/needs.
Personal opinion: (you get what you paid for)
– Go for (3)(4) in case: you want the page delivered fast, love builders, you are a startup or you just want
to test your business on the market.
– Go for (1)(2) in case: you know exactly what you want, you have the patience and budget for it.
Personal Advice: The middle ground between these choices is to buy the HTML template (modify it if you need to) and create a custom theme from It that you will be able to mix with builders and plugins. In this way you will need a moderate budget and gain the best of both worlds by having a solid, secure, and flexible webpage finished in a timely manner with the design you love and the functionality you need.