WordPress – Relative Stylesheets and Scripts, Plus wp-config.php Order

  • infoFull Post Details
    info_outlineClick for Full Post Details
    Date Posted:
    Aug. 18, 2019
    Last Updated:
    Aug. 18, 2019
  • classTags
    classClick for Tags

I recently wanted to force WordPress to make all my <script> and stylesheet (<link>) tags load relative URLs instead of absolute URLs, and ended up learning a bit about the internals of WordPress (probably more than I wanted to know). For reference, I wanted to turn this:

<link rel=’stylesheet’ id=’joshuatzwp-style-css’ href=’http://joshuatz-wp.test/wp-content/themes/joshuatzwp/style.css‘ type=’text/css’ media=’all’ />

Into this:

<link rel=’stylesheet’ id=’joshuatzwp-style-css’ href=’/wp-content/themes/joshuatzwp/style.css‘ type=’text/css’ media=’all’ />

I wanted to do this because I was working with a local development environment, using Laragon on Windows as an Apache host, and proxying through Ngrok or something similar was not working with absolute domain-tied links, for a number of reasons. Furthermore, relative paths that were being passed to wp_enqueue_style were not being preserved.

Click here to jump right to the solution, or read on if you are interested in the details of how WordPress forces absolute paths.

First wrinkle: WP forces absolute links

The first issue I ran into was WordPress does not respect the format you use to pass links into the enqueue system, even if you make 100% sure you pass in a relative link. For example, look at this code:

wp_enqueue_style('joshuatzwp-style',wp_make_link_relative(get_stylesheet_uri()),array(),$cacheBustStamp,'all');

You would expect that the use of wp_make_link_relative would ensure a relative link, but the style tag that this generates actually is absolute with the domain:

<link rel=’stylesheet’ id=’joshuatzwp-style-css’ href=’http://joshuatz-wp.test/wp-content/themes/joshuatzwp/style.css’ type=’text/css’ media=’all’ />

Why is this happening? Well, it is not a fun answer:

WordPress do_item nonsense

To figure out what was going on, I ended up tracing backwards from where the <script> and <link> tags are actually echoed out into the page, to see how that HTML is generated. Here is a rough overview of what that looks like:

None of this knowledge was strictly necessary to force relative paths, but it gives some insight into how the whole process works. I wouldn’t actually want to change any of these files, since they are part of the core of WP, and would be overwitten on a system update anyways.

The solution for relative paths:

The solution here is to hook into a WordPress filter. I’ve written about this before, when trying to add async/defer attributes to scripts and style tags, in this post. In fact, our solution is going to look pretty similar.

Basically, we will hook into the filter for ‘script_loader_tag’, which is the filter for <script> tags, and ‘style_loader_tag’, which is the filter for stylesheet <link> tags. In our hook, we will take the source that WordPress has prefixed and forced into an absolute URL, and remove the domain part that they added.

Here is the code:

function makeInternalLinkRelative($src){
    return str_replace(get_option('siteurl'),'',$src);
}

add_filter('style_loader_src',function($src, $handle){
    return makeInternalLinkRelative($src);
},10,4);
add_filter('script_loader_src',function($src, $handle){
    return makeInternalLinkRelative($src);
},10,4);

Pretty simple, huh! Just add this code to your theme’s (or child theme’s) function.php file, and you should be good to go!

The Solution for Dynamic Domains

If you don’t want to use relative paths, but just want WordPress to know when you are serving it through a different domain (such as when using Ngrok or swapping domains) and automatically adjust all the links, there is another solution we can employ. We can modify the wp-config.php file in the root of our WordPress install to dynamically set the “siteurl”, which as my earlier research pointed out, is used internally by WP to auto-prefix relative links.

Here is what I have added to my wp-config.php file:

$host = $_SERVER['HTTP_HOST'];
$protocol = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT']===443)) ? 'https://' : 'http://';
define('WP_SITEURL', $protocol . $host);
define('WP_HOME', $protocol . $host);

Now, here is the really important thing. Make sure this code comes before this line of code that should already be in the file:

`require_once(ABSPATH . ‘wp-settings.php’);`

This matters because wp-settings.php is responsible for the chain of events that fills in the $base_url variable we were looking at earlier, and is ultimately responsible for prefixing URLs. If that runs before you set the “siteurl” global, it will have the domain from your admin settings, not from our dynamic override. I found this out the hard way!

Leave a Reply

Your email address will not be published. Required fields are marked *