Wordpress is no doubt the most popular blog platform. It’s easy to learn and set up even for people without technical skills. Additionally, you can use plenty of themes both free and premium. However, using premium Wordpress themes may result in decreasing load time and thus hamper your website’s performance. Lower performance means bad user experience and a lower place in SERP rankings. In this blog post, you will find a tutorial on how to create a custom Wordpress theme and improve your website’s performance. You don’t need to have coding skills--basic HTML and CSS knowledge will be enough to customize your own Wordpress theme.
The source of the problem
In my work on site performance I have seen how misleading the Google Insights analysis and especially the message “Reduce Server Response Time TTFB” can be. You receive such a message when the server response arrives with a huge delay. This information combined with its official Google description suggested to me that I should look for improvements at the server side. Yet, all my efforts were in vain. Even moving to another hosting company with much better performance didn’t really help. Nor did using powerful VPS.
When I analyzed the code more thoroughly, I discovered that it wasn’t a server-side issue that was slowing the server’s response. It was a simple front-end problem caused by badly written code behind the WordPress custom themes. Websites using such themes had loading times from 13 to 16 seconds, including a few seconds of TTFB. The theme code was written just for sale and not optimized for better site performance. Today, I still see that this is a common case in many premium WordPress themes.
The problem of WordPress custom themes
Premium themes usually have plenty of different features: sliders, Google Maps, galleries, grid views, fonts, composers, jquery, special jquery plugins, spinners, modernizr, carousels, etc. Each feature usually includes 1 css and 1 js. I found out that a premium theme usually registers more than 10 new .js and more than 10 new .css files. Some themes can even have more than 20.
Additionally, it doesn't really matter if you actually use a given feature on your website. The theme still loads and the browser processes the .js and .css of that feature. Basically, this means that many new lines of code and new files have to be registered in the backend and downloaded by the browsers.
This obviously takes precious time and negatively impacts the overall user experience and increases the bounce rate. The website’s slow loading and rendering time also drag down the SEO rankings—and to some degree, your business.
A simple and an effective solution would be to deregister the unneeded css and js files provided for a premium theme. This will allow you to drastically decrease the loading time from 15 to 3 or 4 seconds!
Still, you can go a step further and build your own custom themes. You do not need to know PHP in order to create a good WordPress theme. Obviously, knowing PHP would be beneficial and allow you to create custom functions and customize the native WordPress functions. But this isn’t critical and you can do well without it. This is as in the case of electricity: you do not have to know exactly how it works to use it effectively.
This tutorial presents a step-by-step guide to developing a simple WordPress theme from scratch and customizing it using HTML/CSS. This will lead to a site that looks just like you want it to and boasts short load times as well.
Requirements
To start creating a WordPress custom theme you’ll need the following:
- Junior front-end background, WordPress, HTML, CSS knowledge as a minimum
- Node.js
- A code editor such as VS Code
- Mysql/MariaDB
- A Linux environment (or XAMPP for Windows)
The WordPress blog should be up and running, for example on the URL http://newsite:81 (you can replace this with the actual URL). I assume the system is ready and meets all the above requirements.
Environment setup
In the already installed WordPress instance theme path, let’s create a new folder that will contain new theme files. Unless changed manually, the default theme location is wp-content/themes.
Let’s create the folder: mkdir newwptheme
In this folder I will create a simple package.json to help me run Gulp for task automation.
The package.json will contain:
{
"name": "New-wp-theme",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"browserslist": [
"last 1 version",
"> 1%"
],
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/preset-env": "^7.8.4",
"browser-sync": "^2.26.7",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^7.0.1",
"gulp-babel": "^8.0.0",
"gulp-concat": "^2.6.1",
"gulp-sass": "^4.0.2",
"node-sass": "^4.12.0"
},
"dependencies": {
"gulp-sourcemaps": "^2.6.5"
}
}
In the root of the newwptheme folder**,** I will also create a simple configuration for Gulp which will automate JS tasks such as:
- Babel, in case you want to add custom JS scripts
- Sass to simplify CSS writing
- Sourcemaps to help with debugging and in-browser development
- Autoprefixer to increase x-browser CSS compatibility
- Browser Sync to synchronize browsers and allow you to test on different devices at the same time
- Reload to reload the browser when new changes are made
- Concat to concatenate stylesheets and scripts
The gulpfile.js will contain:
const gulp = require('gulp');
const babel = require('gulp-babel');
const sass = require('gulp-sass');
const sourcemaps = require('gulp-sourcemaps');
const autoprefixer = require('gulp-autoprefixer');
const browserSync = require('browser-sync');
const reload = browserSync.reload;
const concat = require('gulp-concat');
sass.compiler = require('node-sass');
gulp.task('babel', () =>
gulp
.src('js/*.js')
.pipe(sourcemaps.init())
.pipe(
babel({
presets: ['@babel/env'],
}),
)
.pipe(concat('all.js'))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('.'))
.pipe(reload({ stream: true })),
);
gulp.task('browser-sync', function () {
const files = ['./scss/*.scss', './*.php', './js/*.js'];
browserSync.init(files, {
proxy: 'http://newsite:81',
notify: true,
});
gulp.watch('./scss/**/*.scss', gulp.series(css));
gulp.watch('./js/*.js'), gulp.series('babel');
gulp.watch('./*.php').on('change', browserSync.reload);
});
const css = function () {
return gulp
.src('./scss/main.scss')
.pipe(sourcemaps.init())
.pipe(
sass({
outputStyle: 'compressed',
}).on('error', sass.logError),
)
.pipe(autoprefixer())
.pipe(sourcemaps.write())
.pipe(concat('style.css'))
.pipe(gulp.dest('./'))
.pipe(reload({ stream: true }));
};
const watch = function (cb) {
gulp.watch('./scss/**/*.scss', gulp.series(css));
gulp.watch('./js/*.js'), gulp.series('babel');
gulp.watch('./*.php').on('change', browserSync.reload);
cb();
};
exports.css = css;
exports.watch = watch;
exports.default = gulp.series(css, 'babel', watch, 'browser-sync');
// gulp.series // one by one
// gulp.parallel // altogether
Let’s now prepare the folder structure. In the theme folder I will create the following folders:
- scss
- js
In the scss folder I’ll create a file called main.scss with the following comment:
/*
Theme Name: newtheme
Author: your name
Version: 1.0
License: All rights reserved!
Text Domain: newtheme
*/
As the official WordPress documentation says, this comment is important for WordPress in order to understand that this is a real theme. Without this file it won’t appear in wp-admin → appearance.
Everything is now ready to run node and install all the packages. The following command will install all the dependencies and all the software needed to automate necessary tasks. The following command will also create the node_modules folder, which will contain the dependencies mentioned above:
sergio@sergioLnx:~$ npm
WordPress custom theme development
Header
I will keep the header in a separate php file with the name header.php:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title><?php wp_title(''); ?></title>
<meta charset="UTF-8">
<?php wp_head(); ?>
</head>
<body class="body <?php echo (is_front_page()) ? 'home' : 'not-home'; ?>">
<header class="header">
<section class="header_nav">
<nav class="header_nav__navbar">
<?php
wp_nav_menu(array(
'theme_location' => 'menu-top',
'container' => 'ul',
'menu_class' => 'header_nav_ul',
'menu_id' => 'header_nav_id',
'depth' => 0,
))
?>
</nav>
</section>
</header>
<main class="main">
What here happens:
<?php wp_title(''); ?>
It will get the WordPress page title that will be set up in the wp-admin. In this way, each page, homepage and blogpost can have a different title.
<?php wp_head(); ?>
This will add crucial information, such as scripts, stylesheets and other metadata, to make the website function properly.
<body class="body <?php echo (is_front_page()) ? 'home' : 'not-home'; ?>">
I personally find this information very useful, especially during the styling if you need to give specific styles to the homepage that will now apply to the rest of the site. It will also give the “body” tag the class “home” if it’s the home page, and „not home” if it’s not.
Front page
Now it’s time to create the front-page. In this sample theme the blog posts will appear in a subdirectory, for example example.com/blog/, where the homepage will be a static page. Let’s create the front-page.php file:
<?php /* Template Name: Home */ ?>
<?php get_header(); ?>
<div class="front-page">
<p>This is the homepage and it's working fine</p>
</div>
<?php get_footer(); ?>
What I am doing here is simply adding a short paragraph as the content of the homepage to wrap it between the header and the footer. Additionally, I will add a very important php comment at the top
/* Template Name: Home */
Thus WordPress will know that this is a custom template that can later be set up in one or more WordPress pages.
Footer
As soon as the front page is created, a simple footer can be coded in a new file called footer.php. The following code will be used:
</main>
<footer id="footer" class="footer">
<p>This is the footer</p>
</footer>
<?php wp_footer(); ?>
</body>
</html>
Here I call the wp*footer()*, which works similarly to the wp**head()** function. It adds important information before the closing </body> tag. You can read more about those hooks here .
Wphead and getheader vs wpfooter and getfooter
What’s the difference? In a nutshell, wphead and wp**footer are the functions responsible for adding important information to the page, whereas get*header*** *and** get*****footer will add here all the content that is kept in separate files, header.php and footer.php** respectively.
Blog
WordPress requires every theme to have an index.php file. This is the file responsible for showing the blog posts loop—the main page that lists the posts. Let’s go ahead and create the index.php:
<?php get_header(); ?>
<div class="container container_blog">
<div class="blog-and-sidebar">
<div class="blog">
<?php
$newsite_the_excerpt = get_the_excerpt();
$newsite_headline = substr($newsite_the_excerpt, 0, 100) . '...';
while (have_posts()) : the_post(); ?>
<article class="blog-post-in-loop">
<a class="imagelink imagelink-desktop" href="<?php the_permalink() ?>" title="<?php echo get_the_title(); ?>"><img class="post-image" src="<?php echo get_the_post_thumbnail_url(get_the_ID(), 'full'); ?>"></a>
<h1 class='blogpost_title'><a href="<?php echo the_permalink(); ?>"><?php echo get_the_title(); ?></a></h1>
<p class='post-excerpt'><?php echo get_the_excerpt(); ?></p>
<div class='read-more'><a href="<?php echo the_permalink(); ?>"><?php _e('Read More','newtheme'); ?></a></div>
</div>
</article>
<?php
endwhile;
?>
<div class="pagination-outside">
<div class="pagination">
<?php if (function_exists("pagination")) {
pagination();
} ?>
</div>
</div>
</div>
<?php if (is_active_sidebar('blog_right_sidebar')) : ?>
<aside class="sidebar">
<?php get_sidebar( 'blog_right_sidebar' ); ?>
</aside>
<?php endif; ?>
</div>
</div>
<?php get_footer(); ?>
Once again I wrap the code between the header and the footer.
A <div>tag, whose children are a <div>for the blog content and a <div> for just the sidebar, later calls the posts, as soon as there are posts. It shows them one after another, which is achieved by the while loop:
while (have_posts()) : the_post(); ?>
For each existing blogpost it will create an <article> tag to meet the HTML5 semantics. The article will have an image—the featured image—in the size it was uploaded:
src="<?php echo get_the_post_thumbnail_url(get_the_ID(), 'full'); ?>"
The image will link to the blog post URL:
href="<?php the_permalink() ?>"
and the image title will be the title of the blogpost:
title="<?php echo get_the_title(); ?>"
Below the image, I render the blogpost title inside a <h1> tag and I link it to the blogpost the same way I did it with the featured image. Below the title I render the excerpt of the blogpost via the following function:
get_the_excerpt()
Before ending each loop it adds a “Read more” button with the text domain “newtheme”:
_e('Read More','newtheme');.
This can be useful later, if you use plugins to translate theme strings.
At the end of the loop, I close with:
<?php endwhile; ?>
At the bottom of the blog content page, pagination is needed to allow visitors to change pages and see older posts. This is especially important as the number of blog posts grows. Pagination can be created with the function:
<?php if (function_exists("pagination")) {
pagination();
} ?>
A similar function is created for the sidebar:
<?php if (is_active_sidebar('blog_right_sidebar')) : ?>
<aside class="sidebar">
<?php get_sidebar( 'blog_right_sidebar' ); ?>
</aside>
<?php endif; ?>
However, running the theme right now will result in errors because it calls the functions that have not yet been defined. So it’s now time to take care of custom functions.
Functions
Create a functions.php file. Let’s add to the file the following code blocks:
function admin_bar()
{
if (is_user_logged_in()) {
add_filter('show_admin_bar', '__return_true', 1000);
}
}
add_action('init', 'admin_bar');
This allows you to turn on or off the admin bar when you are logged in and are testing the style.It can also be turned on/off during development. The function, as it is now, will make the admin bar visible.
register_nav_menus(
array(
'menu-top' => __('Top menu', 'newtheme'),
)
);
This will register the navigation menu so you can manage it from the wp-admin panel.
function styles_and_scripts()
{
wp_enqueue_style('main-style', get_stylesheet_uri());
wp_deregister_script('jquery');
}
add_action('wp_enqueue_scripts', 'styles_and_scripts');
This will enqueue the style.css file—the most important css of the theme. It will appear in the header section. Additionally, it dequeues jQuery. I usually develop themes without jQuery, so they load faster.
ffunction newtheme_blog_right_sidebar()
{
$args = array(
'id' => 'blog_right_sidebar',
'class' => 'blog_right_sidebar',
'name' => __('Blog right sidebar', 'newtheme'),
'before_title' => '<h2 class="widget-title">',
'after_title' => '</h2>',
'before_widget' => '<div id="%1$s" class="widget %2$s">',
'after_widget' => '</div>',
);
register_sidebar($args);
}
add_action('widgets_init', 'newtheme_blog_right_sidebar');
This will register the sidebar, which you can then manage from the admin panel, and easily add custom widgets.
function pagination($pages = '', $range = 4)
{
$showitems = ($range * 2)+1;
global $paged;
if(empty($paged)) $paged = 1;
if($pages == '')
{
global $wp_query;
$pages = $wp_query->max_num_pages;
if(!$pages)
{
$pages = 1;
}
}
if(1 != $pages)
{
if($paged > 2 && $paged > $range+1 && $showitems < $pages) echo "<a href='".get_pagenum_link(1)."'>« First</a>";
if($paged > 1 && $showitems < $pages) echo "<a href='".get_pagenum_link($paged - 1)."'>‹ Previous</a>";
for ($i=1; $i <= $pages; $i++) { if (1 != $pages &&( !($i >= $paged+$range+1 || $i <= $paged-$range-1) || $pages <= $showitems ))
{
echo ($paged == $i)? "<span class=\"current\">".$i."</span>":"<a href='".get_pagenum_link($i)."' class=\"inactive\">".$i."</a>";
}
}
if ($paged < $pages && $showitems < $pages) echo "<a href=\"".get_pagenum_link($paged + 1)."\">Next ›</a>";
if ($paged < $pages-1 && $paged+$range-1 < $pages && $showitems < $pages) echo "<a href='".get_pagenum_link($pages)."'>Last »</a>";
}
}
add_theme_support( 'post-thumbnails' );
This will create the pagination function called loop page at the end of the blogpost.
Testing the homepage
To check if the homepage is working, it must be connected. How to do it? First, a style.css file in the theme folder needs to be created. However, instead of creating this file manually, I will have Gulp create it for me. So in the scss folder let’s create another folder for the homepage. We’ll call it front-page and in this folder create a frontpage.scss file (yes, with the underscore, as it simplifies importing). The path will be themes/newtheme/scss/front-page/frontpage.scss
In this file I will just add:
.front-page p {
font-size: 40px;
color: dodgerblue;
}
The partial file I just created frontpage.scss has to be imported in the main.scss file.
To import it, I add the following line to the main.scss file.
@import "front-page/front_page";
I run gulp browser-sync and the site should start in the browser. Let’s go to the wp-admin and create a page called „Home”. Wow. It works!
Testing the blog
Let’s create a new page with the title ”Blog” and the slug blog. So the posts will show in example.com/blog/. If you go to settings → reading, you can choose a page for the posts. Let’s choose the one that has just been created.
If you now go to the site and test it, you should be able to see the blog posts, if they have been created. If not, go ahead and create some blog posts.
There is still something missing. If you click on a blogpost, it will not open. That’s because a single.php file is missing. The file will contain the following code:
<?php get_header(); ?>
<div class="blogsingle">
<div class="blog-single__post-with-sidebar">
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<div class="blog">
<article class="single-article">
<div class="entry_blog singlepageentry">
<div class="singlepostimage">
<?php
the_post_thumbnail('full', array('class' => 'post_thumbnail_common post-image', 'alt' => get_the_title(), 'title' => get_the_title())); ?>
</div>
<h1 class='blogpost_title'><a title="Permanent Link to <?php the_title(); ?>" href="<?php the_permalink() ?>" rel="bookmark"><?php the_title(); ?></a></h1>
<div class="blog-single-text"><?php the_content(); ?></div>
<section class="next-prev">
<div class="next-prev-post">
<div class="single_nav_pre"> <?php previous_post_link(); ?></div>
<div class="single_nav_next"> <?php next_post_link(); ?></div>
</div>
</section>
</div>
</article>
<?php endwhile; ?>
<?php endif; ?>
<?php if (is_active_sidebar('blog_right_sidebar')) : ?>
<aside class="sidebar">
<?php dynamic_sidebar('blog_right_sidebar'); ?>
</aside>
<?php endif; ?>
</div>
</div>
<?php
get_footer();
?>
The single.php file contains the condition: if there is at least one post, create an <article> tag, put the featured image in it, give it a title, and put in the content:
<div class="blog-single-text"><?php the_content(); ?></div>
At the end of the blog post content, this will put a link to the previous and next post to simplify navigation and to create internal links for better SEO results. Additionally, a sidebar will be created at the end of the blog post.
The next step is to create the pages. You can create custom pages and give them a template name as we did for the frontpage:
<?php /* Template Name: NewPage*/ ?>
Alternatively, you can create a page.php file that will work similarly to single.php—it will provide a structure for all the pages.
Everything is now ready for styling. Of course, the sidebar will now appear at the bottom of the pages. You now need to work with the scss, creating the style to make the webpage look good. You may need or want to edit the comments fields to give a custom logic or behavior to the navbar. These, however, are advanced functions that are not required for a basic WordPress blog to work well.
Summary
By applying the guidelines explained in this tutorial, you will be able to create a WordPress custom theme extremely quickly and have it rank up high in the search results. Just remember the following:
- create a good scss
- avoid using jQuery unless it’s strictly necessary
- use as few plugins as possible—they add custom scripts that increase the overhead
I hope this tutorial has been helpful for you. To test it on your PC, download the code from my Github . Please let me know if you see something I can enhance or correct.