Now Bulletproof SEO through Dynamic Rendering Implementation


Last week I have encountered huge problems with my previous SEO approach. Google crawler stopped for any reason to render the app. First I thought it would be related to the Lazy Loading Routes I’ve implemented in the router.js. But after some testing I found out, it was not the case (even though they actually should have cause some troubles as the bundles are loaded asynchronously). So I have completely refactored the theme and have implemented an approach I ‘ve dropped in February of this year as I’ve believed the claim by Vue.js docs, that Google actually can render JavaScript “just fine”:

“Note that as of now, Google and Bing can index synchronous JavaScript applications just fine.”

ssr.vuejs.org
Note that as of now, Google and Bing can index synchronous JavaScript applications just fine.

But obviously it’s not always the case and the latest official statement by Google (last update Mai 10, 2019) is:

“Currently, it’s difficult to process JavaScript and not all search engine crawlers are able to process it successfully or immediately.”

developers.google.com

So they recommend to use “dynamic rendering as a workaround solution to this problem. Dynamic rendering means switching between client-side rendered and pre-rendered content for specific user agents.”

Conclusion: Google bots and crawlers still do not fully support JavaScript rendering

But no problem. Even better. As there are not only google bots out there, which you might want to give access to your content. And the workaround was actually not a huge deal. I’ve just added a simple PHP function into the themes functions.php:

function technomad_bot_detected() {

return (

isset($_SERVER['HTTP_USER_AGENT'])  && 

preg_match('/bot|googlebot|crawl|slurp|google-structured-data-testing-tool|spider|linkedinbot|bingbot|mediapartners/i', $_SERVER['HTTP_USER_AGENT'])

);

}

and then an if-condition in each template. Here for example the page.php

<?php


get_header();


?>


<body>


<?php


if ( technomad_bot_detected() ) {


get_template_part( "template-parts/bot-markup/post" );


} else {


initial_loader();


if ( have_posts() ) {


// Load posts loop.

while ( have_posts() ) {

the_post();

$data                          = [];

$data["post"]["title"]         = get_the_title();

$data["post"]["content"]       = apply_filters( "the_content", get_the_content() );

$data["post"]["datePublished"] = get_the_date();

$data["post"]["dateModified"]  = get_the_modified_date();

}

}


?>


    <script>

        technomad.initialData = <?php print_r( json_encode( $data ) )?>;

    </script>


    <div id="app"></div>


<?php

}


get_footer();


?>


</body>

</html>

The bot template for page.php and post.php looks like this:

<?php


get_template_part( "template-parts/bot-markup/dequeue-scripts" );

get_template_part( "template-parts/bot-markup/header-nav" );


if ( have_posts() ) {


$publishedDate     = get_the_date();

$publishedDateTime = date( 'Y-m-d', strtotime( $publishedDate ) );

$modifiedDate      = get_the_modified_date();

$modifiedDateTime  = date( 'Y-m-d', strtotime( $modifiedDate ) );


// Load posts loop.

while ( have_posts() ) {

the_post();

?>

        <header>

            <h1>

<?php the_title() ?>

            </h1>

            <time datetime="<?php echo $publishedDateTime ?>" itemprop="datePublished"><?php echo $publishedDate ?></time>

            <br>

<?php if ( $publishedDate !== $modifiedDate ) { ?>

                <time datetime="<?php echo $modifiedDateTime ?>" itemprop="dateModified"><?php echo $modifiedDate ?></time>

<?php } ?>

        </header>

        <main>

<?php the_content(); ?>

        </main>

<?php

}

}

get_template_part( "template-parts/bot-markup/footer" );

Inside template-parts/bot-markup/dequeue-scripts.php I dequeue Vue bundle scripts for the bots as these cause errors in the bot version:


<?php

wp_dequeue_script( "technomad-app-bundle" );

wp_deregister_script( "technomad-app-bundle" );

wp_dequeue_script( "babel-polyfill-bundle" );

wp_deregister_script( "babel-polyfill-bundle" );


The header-nav.php template is also very simple:

<?php get_template_part( "template-parts/bot-markup/mobile-friendly-style" ) ?>

<nav>

<?php

wp_nav_menu( array(

'theme_location' => 'header-menu',

'container'      => false,

'menu_class'     => false

) );

?>

</nav>

Same with footer.php:

<footer>

    <nav>

<?php



/**

* get registered menus (see functions.php)

* and check if any menus in backend are assigned to them,

* if so - load the (footer) menu

**/

foreach ( get_registered_nav_menus() as $key => $value ) {


if ( has_nav_menu( $key ) && strpos( $key, "footer" ) === 0 ) {


wp_nav_menu( array(

'theme_location' => $key,

'container'      => false,

) );



}

}

?>

    </nav>

</footer>

That’s actually it. I’ve also refactored all the main Vue components (Page, Post and Posts) and some related functions, so the code is now much cleaner.

Headless WordPress + Server Side Rendering with Nuxt.js vs. Dynamic Rendering Implementation

Now it’s quite a bit of additional code – you have to maintain three templates: Vue, PHP and the bot markup. But the PHP code and bot markup are very simple structured – the most difficult part is still on the Vue side. On the other hand Server Side Rendering with Nuxt.js is much much more massive pain in the a..: you can’t use window object, you have to setup an extra Node.js server and as mentioned 50 times in other pages/posts: Nuxt.js doesn’t properly work with keep-alive. Which also forces you to code your own implementation using local storage. So at the bottom the dynamic rendering implementation is the way to go. At least regarding WordPress and WooCommerce Single Page Applications.

Headless WordPress + Pre-Rendering vs. Dynamic Rendering Implementation

Seriously? Prerendering 200 posts/pages/products or even more? I am not an expert with this approach but in case of WordPress / WooCommerce it is obviously not the way to go if you have to deal with non-tech users.

Killer Argument

In both cases (SSR & Pre-Rendering) there is one killer argument against them: if you want to sell the theme as a download without technical support (by default) like it’s the case with all the other premium WordPress themes, then you can’t expect the usually non-technical user to setup a Node.js server or to setup pre-rendering.

To me it seems like a plenty front end or full stack JavaScript developers are forcing the WordPress developers to accept their headless dogma, which is totally ok within a well financed startup or even a corporation. But in case of WordPress it’s simply not acceptable, IMHO. We, the (freelancing) WordPress devs have to fight back 😀

Comments