START here

How I built this WordPress website?

What theme, plugins, and custom code I’ve used to build this website (and other BikeGremlin websites). As with most of my other articles, this is intended primarily for my own reminder – and if it helps anyone else, great! 🙂
In a separate article, I explained the initial WordPress configuration that doesn’t differ much from website to website, while other articles give detailed tutorials for the following:


Table Of Contents (T.O.C.):

  1. Introduction
  2. Child theme custom code
  3. Plugins choice
  4. Theme choice and configuration
    4.1. Menus
    4.2. Final touches
  5. Running WordPress with PHP 8.0
  6. Conclusion


1. Introduction

In 2015, I was looking for a way to put my notes, work, and thoughts online, so they are available for me, and the others, wherever they are. Of all the available options, WordPress seemed (and still seems) like the least bad choice – considering all the pros and cons. It still seems that way.

By mid-2021, I’ve decided it’s about time to make my websites look decently, and improve the user interface, including the search option. That way they’ll better serve me and the others.

I wrote a separate article about the website redesign process – there you can have a bit of a laugh, looking at the pictures from 2015, 2018, etc. 🙂 Here, I’ll explain the technical aspect – how and what I did to make the websites work, and look as they are now.

I’ll start “from the back,” with the custom code shown first. That did come at the end, as final touches, but it’s easier to follow the article when it starts with the simplest things.

– T.O.C. –


2. Child theme custom code

I made a WordPress child theme, and added the following code to its style.css:
– I recommend making a child theme at the start and putting all the edits there.

/* BEGIN editing social icons size */
.wp-block-social-links {
    font-size:50px;
}
/* END editing social icons size */


/* BEGIN google search max-width */
#wgs_widget_wrapper_id {
  max-width:600px;
  min-height:40px;
}
/* END google search max-width */


/* BEGIN small hero image for mobile */
@media (max-width: 935px) {
    .page-hero {
        background-image: url( 'https://bike.bikegremlin.com/wp-content/uploads/2021/09/hero-image-mobile-retina-bikegremlin.jpg' ) !important;
    }
}
/* END small hero image for mobile */


/* BEGIN desktop hero image height */
@media (min-width: 936px) {
    .page-hero {
        height: 300px !important;
    }
}
/* END desktop hero image height */


/* BEGIN disabling the horizontal scrols bar */
body {
    overflow-x: hidden;
}
/* END disabling the horizontal scrols bar */


/* BEGIN aligning image captions to the left margin */
.wp-block-image figcaption {
    text-align: left;
}
/* END aligning image captions to the left margin */


/* BEGIN showing only updated post date */
.posted-on .updated {
    display: inline-block;
}

.posted-on .updated + .entry-date {
    display: none;
}

.posted-on .updated:before {
    content: "Updated: ";
}
/* END showing only updated post date */

/* BEGIN text background for gallery images */
.wp-block-gallery.has-nested-images figure.wp-block-image figcaption {
  background: linear-gradient(160deg,rgb(0 0 0 / 95%),rgb(0 0 0 / 69%) 70%,transparent);
  bottom: 0;
  box-sizing: border-box;
  color: #fff;
  font-size: 16px;
}
/* END text background for gallery images */


The comments explain what each part does – I don’t think it needs further explaining.

In the child theme’s functions.php file, I added the following:

/ BEGIN Disable image format auto creation by WordPress
function add_image_insert_override( $sizes ){
    unset( $sizes[ 'thumbnail' ]);
    unset( $sizes[ 'medium' ]);
    unset( $sizes[ 'medium_large' ] );
    unset( $sizes[ 'large' ]);
    unset( $sizes[ 'full' ] );
    return $sizes;
}
add_filter( 'intermediate_image_sizes_advanced', 'add_image_insert_override' );
// END Disable image format auto creation by WordPress

// BEGIN disable auto 768px image display on mobile
add_filter( 'wp_calculate_image_srcset', '__return_false' );
// END disable auto 768px image display on mobile

// BEGIN Disable WordPress Lazy Load images
add_filter( 'wp_lazy_loading_enabled', '__return_false' );
// END Disable WordPress Lazy Load images

// BEGIN smooth scroll for all anchor links
add_filter( 'generate_smooth_scroll_elements', function( $elements ) {
  $elements[] = 'a[href*="#"]:not(.generate-back-to-top)';
  
  return $elements;
} );
// END smooth scroll for all anchor links

// BEGIN disable header Element for AMP
add_filter( 'generate_element_display', function( $display, $element_id ) {
    // Tell the non-AMP Element not to appear.
    if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() && 13457 === (int) $element_id ) {
        $display = false;
    }

    return $display;
}, 10, 2 );
// END disable header Element for AMP

// BEGIN Google analytics
function ns_google_analytics() { ?>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-GOOGLE-TRACKING-CODE"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-GOOGLE-TRACKING-CODE');
</script>
  <?php
  }
  
add_action( 'wp_head', 'ns_google_analytics', 10 );
// END Google analytics


// BEGIN Hiding the admin-bar for the non-editors
function remove_admin_bar() {
	if (!current_user_can('edit_posts')) {
		show_admin_bar(false);
	}
}

add_action('after_setup_theme', 'remove_admin_bar');
// END Hiding the admin-bar for the non-editors


// BEGIN ads for amp - goes into footer
$print_amp_auto_ads = function() {
	$ad_client = 'ca-pub-GOOGLE-AD-CODE'; // @your client  ID
	if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
		?>
		<amp-auto-ads type="adsense" data-ad-client="<?php echo esc_attr( $ad_client ); ?>"></amp-auto-ads>
		<?php
	}
};
// For Paired/Native mode.
add_action( 'wp_footer', $print_amp_auto_ads );
// END ads for amp - goes into footer


// BEGIN list child pages without listing pages of the same hierarchy level
function relja_list_child_pages() {
  global $post;
  if ( is_page() )
  $childpages = wp_list_pages( 'sort_column=menu_order&title_li=&child_of=' . $post->ID . '&echo=0' );
  if ( isset($childpages )) {
    $string = '<ul>' . $childpages . '</ul>';
  }
 else{
     $string = '';
 }
  return $string;
}
add_shortcode('relja_child_pages', 'relja_list_child_pages');
// END list child pages without listing pages of the same hierarchy level


// NEW BEGIN Google Auto Ads
function ns_google_autoads() { ?>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-GOOGLE-AD-CODE"
     crossorigin="anonymous"></script>
<?php
}

add_action( 'wp_head', 'ns_google_autoads', 10 );
// NEW END Google Auto Ads


// BEGIN Display breadcrumbs
function the_breadcrumb()
{

	$lg_home_name ="Home";
	$lg_archive_by_category = "";
    $lg_search_results_for = "Search:";
    $lg_posts_tagged = "Keyword:";
    $lg_articles_posted_by  = "Author:";


    $showOnHome = 0; // 1 - show breadcrumbs on the homepage, 0 - don't show
    $delimiter = '&raquo;'; // delimiter between crumbs
    //$home = 'Home'; // text for the 'Home' link
    $home = $lg_home_name; // text for the 'Home' link
    $showCurrent = 1; // 1 - show current post/page title in breadcrumbs, 0 - don't show
    $before = '<span class="current">'; // tag before the current crumb
    $after = '</span>'; // tag after the current crumb

    global $post;
    $homeLink = get_bloginfo('url');
    if (is_home() || is_front_page()) {
        if ($showOnHome == 1) {
            echo '<div id="crumbs"><a href="' . $homeLink . '">' . $home . '</a></div>';
        }
    } else {
        echo '<div id="crumbs"><a href="' . $homeLink . '">' . $home . '</a> ' . $delimiter . ' ';
        if (is_category()) {
            $thisCat = get_category(get_query_var('cat'), false);
            if ($thisCat->parent != 0) {
                echo get_category_parents($thisCat->parent, true, ' ' . $delimiter . ' ');
            }
            //echo $before . 'Archive by category "' . single_cat_title('', false) . '"' . $after;           
            echo $before .$lg_archive_by_category. ' ' . single_cat_title('', false) . '' . $after;

        } elseif (is_search()) {
            //echo $before . 'Search results for "' . get_search_query() . '"' . $after;
            echo $before  .$lg_search_results_for.' "' . get_search_query() . '"' . $after;
        } elseif (is_day()) {
            echo '<a href="' . get_year_link(get_the_time('Y')) . '">' . get_the_time('Y') . '</a> ' . $delimiter . ' ';
            echo '<a href="' . get_month_link(get_the_time('Y'), get_the_time('m')) . '">' . get_the_time('F') . '</a> ' . $delimiter . ' ';
            echo $before . get_the_time('d') . $after;
        } elseif (is_month()) {
            echo '<a href="' . get_year_link(get_the_time('Y')) . '">' . get_the_time('Y') . '</a> ' . $delimiter . ' ';
            echo $before . get_the_time('F') . $after;
        } elseif (is_year()) {
            echo $before . get_the_time('Y') . $after;
        } elseif (is_single() && !is_attachment()) {
            if (get_post_type() != 'post') {
                $post_type = get_post_type_object(get_post_type());
                $slug = $post_type->rewrite;
                echo '<a href="' . $homeLink . '/' . $slug['slug'] . '/">' . $post_type->labels->singular_name . '</a>';
                if ($showCurrent == 1) {
                    echo ' ' . $delimiter . ' ' . $before . get_the_title() . $after;
                }
            } else {
                $cat = get_the_category();
                $cat = $cat[0];
                $cats = get_category_parents($cat, true, ' ' . $delimiter . ' ');
                if ($showCurrent == 0) {
                    $cats = preg_replace("#^(.+)\s$delimiter\s$#", "$1", $cats);
                }
                echo $cats;
                if ($showCurrent == 1) {
                    echo $before . get_the_title() . $after;
                }
            }
        } elseif (!is_single() && !is_page() && get_post_type() != 'post' && !is_404()) {
            $post_type = get_post_type_object(get_post_type());
            echo $before . $post_type->labels->singular_name . $after;
        } elseif (is_attachment()) {
            $parent = get_post($post->post_parent);
            $cat = get_the_category($parent->ID);
            $cat = $cat[0];
            echo get_category_parents($cat, true, ' ' . $delimiter . ' ');
            echo '<a href="' . get_permalink($parent) . '">' . $parent->post_title . '</a>';
            if ($showCurrent == 1) {
                echo ' ' . $delimiter . ' ' . $before . get_the_title() . $after;
            }
        } elseif (is_page() && !$post->post_parent) {
            if ($showCurrent == 1) {
                echo $before . get_the_title() . $after;
            }
        } elseif (is_page() && $post->post_parent) {
            $parent_id  = $post->post_parent;
            $breadcrumbs = array();
            while ($parent_id) {
                $page = get_page($parent_id);
                $breadcrumbs[] = '<a href="' . get_permalink($page->ID) . '">' . get_the_title($page->ID) . '</a>';
                $parent_id  = $page->post_parent;
            }
            $breadcrumbs = array_reverse($breadcrumbs);
            for ($i = 0; $i < count($breadcrumbs); $i++) {
                echo $breadcrumbs[$i];
                if ($i != count($breadcrumbs)-1) {
                    echo ' ' . $delimiter . ' ';
                }
            }
            if ($showCurrent == 1) {
                echo ' ' . $delimiter . ' ' . $before . get_the_title() . $after;
            }
        } elseif (is_tag()) {
            //echo $before . 'Posts tagged "' . single_tag_title('', false) . '"' . $after;
			echo $before . $lg_posts_tagged .' "' . single_tag_title('', false) . '"' . $after;
        } elseif (is_author()) {
            global $author;
            $userdata = get_userdata($author);
            //echo $before . 'Articles posted by ' . $userdata->display_name . $after;
            echo $before . $lg_articles_posted_by.' ' . $userdata->display_name . $after;
        } elseif (is_404()) {
            echo $before . 'Error 404' . $after;
        }
        if (get_query_var('paged')) {
            if (is_category() || is_day() || is_month() || is_year() || is_search() || is_tag() || is_author()) {
                echo ' (';
            }
            echo __('Page') . ' ' . get_query_var('paged');
            if (is_category() || is_day() || is_month() || is_year() || is_search() || is_tag() || is_author()) {
                echo ')';
            }
        }
        echo '</div>';
    }
}

add_action( 'generate_before_main_content', 'tabela_zaglavlje' );
function tabela_zaglavlje(){
  if (function_exists('the_breadcrumb')){
  the_breadcrumb();
  echo "<hr style='margin:10px 0 20px 0;'>";
  }
}
// END Display breadcrumbs

Again, the comments within the code are self-explanatory. I wrote a separate article about Google ads (how to add Google AdSense to WordPress), but the code under “// NEW BEGIN Google Auto Ads” is necessary for it to work nicely.

Explanation for the disable header Element for AMP code.

– T.O.C. –


3. Plugins choice

I only chose plugins that have a free version on wordpress.org. For a plugin to be listed there, it needs to pass relatively strict checks, which eliminates at least some of the risk.
– In a separate post I wrote in more detail on how to choose a good WordPress plugin.

Providing a list of used theme and plugins is probably not the wisest choice security-wise, but I think that in this case, the informative-educational aspect outweighs that. So here’s a list of used plugins sorted alphabetically (using affiliate links for the paid ones):

  1. Advanced Database Cleaner Pro (affiliate link)
    After years of using WordPress, the database was full of garbage, deleted themes and plugins leftovers etc. This plugin is probably the best option for sorting that out. Very easy to use, and very effective.
  2. AMP
    If you want an AMP website version, this plugin did best during my testing (what is AMP?).
    Update:
    After weighing all the pros and cons, and based on the preliminary results of my AMP case study, I’ve decided to ditch AMP.
  3. Easy WP SMTP
    Probably the best plugin for getting your site to send emails properly.
  4. List category posts
    To easily make website contents page.
  5. LiteSpeed Cache
    The best WordPress caching plugin.
  6. Newsletter
    Making proper, double-opt-in mailing lists. I’m not a fan of mailing lists, so I’m seriously considering removing this plugin and this option.
    Update: I’ve decided to ditch the newsletter and let people rely on RSS feed and my News page.
  7. Polylang
    Free solution for multilingual websites. It has served me well over the years, though my “main,” cycling-related websites run separate WordPress installs for each language, for an extra performance, stability and flexibility boost. I’ll write a separate article on WordPress multilingual options, pros and cons.
  8. reCaptcha by BestWebSoft
    Greatly reduces the number of spam comments. I’ll write more on this too when I find the time.
    I had to disable (“Suppress”) this plugin within the AMP plugin options – because it was making problems with AMP page versions on Android phones. My problem report on the reCaptcha plugin support forum.
    Update: I’ve built a forum and switched to a better solution for preventing WordPress comment spam. So, I’m no longer using this, nor the next plugin from this list:
  9. Subscribe To Comments Reloaded
    Visitors post a question, and my website emails them when there’s been a reply. Cool! 🙂
  10. The SEO Framework (also known as TSF)
    This is simply the best WordPress SEO plugin.
  11. The SEO Framework – Extension Manager
    TSF add-on, about which I wrote in great detail in the TSF Pro (paid version) plugin review.
  12. Wordfence Security
    My choice for extra WordPress security, with all its pros and cons. I wrote about how to configure WordFence Security.
  13. WP Google Search
    It made it easier for me to configure the smart Google search on my websites, so it also looks good. WordPress.org warns that this “plugin hasn’t been tested with the latest 3 major releases of WordPress.” Well, I’ve tested it, and it works fine. 🙂 The developers are active on their wp.org support forum. They just don’t publish any updates because the plugin is simple and it just works.
    Update: I’ve disabled this plugin and implemented search on a separate search page. The plugin was buggy on some devices and with some optimizations (like Cloudflare Rocket Loader).

Next to some of the above-listed plugins, I’ve placed links to the articles explaining how they are installed and configured. I’ll probably write similar articles for the other plugins as well, it’s just a matter of time.

It’s better to have fewer plugins. My first candidates for removal are the Newsletter, and Subscribe To Comments Reloaded plugins. They look like a good, practical idea, but I don’t think many website visitors are using them.
I hate boring newsletters, but BikeGremlin newsletters are sent only once per year, or even less frequently – only when some really important info needs to be conveyed.

I’m also considering replacing the WP Google Search with a bit of custom code. Polylang still looks like “the least evil” of all the available multilingual options, at least for my use case.

– T.O.C. –


4. Theme choice and configuration

As far as I’m concerned, GeneratePress is the best WordPress theme. 🙂 For this last redesign, I decided to pay for the GeneratePress Premium (affiliate link).

It allows me to easily edit the visuals and basic website functions, without making it too slow like most page builders do.

The first thing I wanted to solve was the header – so that the front page has a header image, while the other pages don’t.

GeneratePress Premium header configuration options
GeneratePress Premium header configuration options
Picture 1

This way I easily added a WP Google Search plugin’s shortcode for the Google search field into the header. Originally I had added the search code, but WP Google Search plugin formatted the look of the search field and button a lot nicer than I could manage. 🙂

CSS code that limits the search field width so that it looks nice (already shown in chapter 2):

/* BEGIN google search max-width */
#___gcse_0 {
    max-width:600px;
}
/* END google search max-width */

In a separate article, I explained how to add a Google Programmable search to a WordPress website.

After that, I used GeneratePress Hooks visual guide (cheat sheet) and used hooks. Documentation this good really makes the job a lot easier.

That way I set the header of all the pages, except the home page, to show Google AdSense ads, with the website search bar below. It’s this easy with GP Premium:

GeneratePress Premium hook configured
GeneratePress Premium hook configured
Picture 2

I did a similar thing for the Google suggested content (from my websites) at the bottom of each page (except the front page). For that, I used the hook “generate_after_main_content”.

Most other changes were done using the WYSIWYG (read “wiz ee wig” – showing the look of how it will appear in the production version, in real-time):

Customizing the website look and functions using GeneratePress Premium
Customizing the website look and functions using GeneratePress Premium
Picture 3

I could choose menu options or just click on a blue pencil icon to edit the section I want. Very easy and fast.

This one, like most other paid themes, has dozens of options, but with GeneratePress they are logically structured and easy to navigate.

My choice of the article featured images was to make them descriptive, not good-looking. So that with a quick glance at an article list, I can tell what each article is about, without reading the title. In order to keep the article lists practical, I set them to be displayed in two columns, and limited the featured image height display to 200 pixels:

Customizing how post-lists are displayed with GeneratePress Premium
Customizing how post-lists are displayed with GeneratePress Premium
Picture 4

In the following years, I intend to replace the featured images with better-looking ones, while keeping them descriptive (I can’t do it all right away, there are hundreds of articles). I also plan to add a short description of each article, to be displayed in the article lists.

I checked the option for smooth scrolling to links that lead to a different section of the same page (like chapters, T.O.C. etc.) – so the screen doesn’t just instantly snap in a new place when you click on ” – T.O.C. – ” and similar (with a “where did I get now?!” reaction). These kinds of links are also called “anchor” links and they start with a ” # ” sign.

Enabling the smooth scroll option
Enabling the smooth scroll option
Picture 5

That was Masha’s idea, I just implemented it. All the custom code is shown in chapter 2, and this part of that code “turns” all the links of the existing pages (that point to another section of the same page) into smooth scrolling links:

// BEGIN smooth scroll for all anchor links
add_filter( 'generate_smooth_scroll_elements', function( $elements ) {
  $elements[] = 'a[href*="#"]:not(.generate-back-to-top)';
  
  return $elements;
} );
// END smooth scroll for all anchor links

Once I had tuned every detail as Masha designed it, 🙂 I exported the configuration. Then I could import it to my other websites, saving myself a lot of time.
Most paid themes offer export and import options.

– T.O.C. –


4.1. Menus

I made two menus:

  • Top menu – displayed only on desktop computers, containing a few important links and a small house icon leading to the home page.
  • Main menu (blue, with white text) – displayed always. It is configured to disappear when you scroll down, leaving more space for reading the articles – and to slide back once you start scrolling up, so all the options are easily available right away. It contains all the important links.

I added the house icon to the menu this way:

Adding an icon (image) to a WordPress menu
Adding an icon (image) to a WordPress menu
Picture 6
  • In the URL field, I entered the path where I want the menu link to lead – a hyperlink “https://io.bikegremlin.com/” in this case.
  • In the Link Text field, I entered HTML code for showing an image, that leads to an image I had previously uploaded to the server (website).

The code from this example, with a bolded path to the image for easier understanding:

<img src="https://io.bikegremlin.com/wp-content/uploads/2021/09/home-icon-25x19-1.png" alt="Home" />

A more elegant way of doing this is to use Font Awesome font to show a house icon. My solution is more robust because it will work even with ancient computers, or computers that won’t display the Font Awesome, for whatever reason.

In the website’s Header options (Customizing ▸ Layout ▸ Header), I had to disable the “Sticky” menu option for mobile phones. My huge menus with a lot of categories didn’t work well with that option enabled.

– T.O.C. –


4.2. Final touches

How I made the front page sections “Highlighted” and “Partners” have a grey background?

BikeGremlin front page "Highlighted" section
BikeGremlin front page “Highlighted” section
Picture 7

I added a Gutenberg block called “Columns” and set it to take the full width – by choosing the first given option: “100”.

In that block’s options, I set the background colour to custom and entered the value ” #f5f5f5 “.

Setting a custom backround colour for the "Columns" Gutenberg block
Setting a custom background colour for the “Columns” Gutenberg block
Picture 8

After this, I simply added all the blocks I wanted to fit inside that block as if the “Columns” block were a page. To get the layout shown in the picture above, I added a “Columns” section, setting it to a two-column “50/50” layout, and placed the elements (more blocks) inside each of the two halves.

The first “Columns” block with the custom background colour gave the entire section the uniform grey background colour, without any white background showing between the sections.

– T.O.C. –


5. Running WordPress with PHP 8.0

WordPress runs using the PHP programming language.

Static HTML websites vs dynamic websites, like WordPress
Static HTML websites vs dynamic websites (like WordPress) that use MySQL database and PHP
Picture 9

My websites were using the second latest PHP version 7.4. Even though I’m not a fan of constant updates of everything, since I was already doing a major website overhaul, I wanted to test and configure BikeGremlin sites to work with PHP 8.0 and be done with it… at least for a while:

PHP versions support (EOL - End Of Life) timeline
PHP versions support (EOL – End Of Life) timeline
Picture 10

The first thing to do was to thoroughly check the redesigned websites on PHP 7.4. It is important to always make one change at a time, otherwise it’s very difficult to determine a problem cause in case of any glitches.

After testing with PHP 7.4, I checked if all the plugins and the theme are PHP 8.0 compatible and when that was confirmed, I started testing in a staging environment. It all went smoothly, apart from one error in my 5 years old custom code, that PHP 7.4 ran with no complaints, while PHP 8.0 also ran it, but warned me to fix it.

I wrote an entire article about the “peculiar PHP 8.0 WordPress error.” 🙂

After having fixed the code and tested it all, my websites were ready for PHP 8.0! In a separate article, I explained how to choose and configure the PHP version on a hosting server.

Initial tests show that websites work fine, even a bit faster now:

WordPress running on PHP 7.4 vs PHP 8.0
WordPress running on PHP 7.4 vs PHP 8.0
Picture 11

– T.O.C. –


6. Conclusion

Experienced good developers would probably have opted for a custom-made theme – though that choice also depends on the budget, i.e. how much are they getting paid for the job. I’m afraid that in this case, my custom code would probably have been less well optimized compared to the code a good premium WordPress theme creates.

The websites are working nicely – they are stable and relatively fast. In other words: I’m happy and waiting for the visitors’ feedback. 🙂

Any suggestions for improvements and corrections are welcome – use the comment section below.

If you’re interested: a list of all the WordPress website-related costs.

– T.O.C. –


Please use the BikeGremlin.net forum for any comments or questions.

If you've found any errors or lacking information in the article(s) - please let me know by commenting on the BikeGremlin forum.
You can comment anonymously (by registering with any name/nickname), but I think it is good to publicly document all the article additions (and especially corrections) - even if their author chooses to remain anonymous.

Skip to content