Patching .htaccess for SEO

By mandclu, Sat, 03/25/2023 - 11:58
Crates stacked in a warehouse

Anyone who has ever gone through the process of trying to maximize their site's Lighthouse or PageSpeed Insights score will have seen suggestions to increase the cache lifetime of static assets: at least 1 year for assets like images and fonts, and at least 1 month for assets like CSS and Javascript files. As noted in the linked page, it is possible to set this cache lifetime site-wide, but if you want to set a shorter cache lifetime for your content than your static assets, we can achieve this with minimal effort.

The easiest way to set the cache lifetime for static assets specifically (for sites hosted on Apache webservers) is by modifying Drupal's default .htaccess file.

If you're hosting your own webfonts (as I recommended in a previous post) you'll want to define a type for them. Near the top of the .htaccess, where the type declarations are being added, add one for woff2, the preferred format when self hosting your fonts. 

# Add type for woff2 fonts.
AddType application/font-woff2 woff2

If you're a "belt and suspenders" type of person, you may choose to also host additional formats for legacy browsers. If that's the case, add type declarations for those as well.

Next, we need actually set the cache lifetime for our assets. Further down, within the <IfModule mod_expires.c> block, add lines that will set our longer cache lifetimes.

  # Add longer caching for static files.
  ExpiresByType image/jpg "access plus 1 year"
  ExpiresByType image/jpeg "access plus 1 year"
  ExpiresByType image/gif "access plus 1 year"
  ExpiresByType image/png "access plus 1 year"
  ExpiresByType application/font-woff2 "access plus 1 year"
  ExpiresByType text/css "access plus 1 month"
  ExpiresByType application/pdf "access plus 1 month"
  ExpiresByType text/x-javascript "access plus 1 month"
  ExpiresByType application/x-shockwave-flash "access plus 1 month"
  ExpiresByType image/x-icon "access plus 1 year"

Obviously, if you added more type declarations for your additional font formats, you'll want to set longer lifetimes for those too.

At this point, all the assets on your site will be served with updated cache headers, and you're ready to re-test the performance of your site.

Getting Patched In

All that is great, except that the next time you use composer (you are using composer, right?) the default scaffolding will (helpfully?) replace your custom .htaccess file with a default one. If you're using git (as I hope you are) you can simply checkout the modified version to bring back your updates, though you should always do a git diff first to make sure there aren't other updates you're missing out on.

All of this works fine, but you may find yourself having to do this same git checkout dance in every environment where you're using composer to manage your dependencies. Fortunately, there's a better way: write a patch, so that your changes are always added to the default .htaccess. There are a couple of steps to get this working.

First, we're going to make sure we have a package installed to help us alter the scaffold files. In your terminal, add the project:

composer require drupal/core-composer-scaffold

Next, we need to manually add a base section to the bottom of our composer.json that will instruct this new package to find and apply a patch to our .htaccess file. If adding this at the end, as shown here, don't forget to add a comma to the end of the closure for the previous section.

    "scripts": {
        "post-drupal-scaffold-cmd": [
            "patch -p1 < patches/htaccess-cache.patch"
        ]
    }

Finally, we need to actually write our changes to this patch file. If you had previously committed the default .htaccess file and haven't yet committed your changed file, this step is very simple.

git diff > patches/htaccess-cache.patch

The above assumes that the .htaccess changes are the only unstaged changes since the last commit. If you have other changes you can add the path to your .htaccess file after the diff command.  My final output for the patch looked like:

diff --git a/web/.htaccess b/web/.htaccess
index 128e4fbc..49c2b3a9 100644
--- a/web/.htaccess
+++ b/web/.htaccess
@@ -22,6 +22,9 @@ DirectoryIndex index.php index.html index.htm
 AddType image/svg+xml svg svgz
 AddEncoding gzip svgz
 
+# Add type for woff2 fonts.
+AddType application/font-woff2 woff2
+
 # Most of the following PHP settings cannot be changed at runtime. See
 # sites/default/default.settings.php and
 # Drupal\Core\DrupalKernel::bootEnvironment() for settings that can be
@@ -36,6 +39,18 @@ AddEncoding gzip svgz
   # Enable expirations.
   ExpiresActive On
 
+  # Add longer caching for static files.
+  ExpiresByType image/jpg "access plus 1 year"
+  ExpiresByType image/jpeg "access plus 1 year"
+  ExpiresByType image/gif "access plus 1 year"
+  ExpiresByType image/png "access plus 1 year"
+  ExpiresByType application/font-woff2 "access plus 1 year"
+  ExpiresByType text/css "access plus 1 month"
+  ExpiresByType application/pdf "access plus 1 month"
+  ExpiresByType text/x-javascript "access plus 1 month"
+  ExpiresByType application/x-shockwave-flash "access plus 1 month"
+  ExpiresByType image/x-icon "access plus 1 year"
+
   # Cache all files and redirects for 2 weeks after access (A).
   ExpiresDefault A1209600

Profit!

Will the above steps make your site's pages load in a flash, and shoot them to the top of every relevant Google search? Sadly, it's not quite that simple and the results aren't that extreme. In truth, most visitors won't really notice the difference within a single visit. As part of an overall effort to optimize your site for performance and SEO, however, these are a few simple steps to address suggestions Drupal site owners often see during performance testing. Collectively they make up one step in the larger journey of optimizing your site, but something you can achieve with minimal effort.

Comments5

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.

max (not verified)

11 months ago

which data is in the patch file? the whole modified .htaccess data?

mandclu

10 months 1 week ago

In reply to by max (not verified)

Thanks for the suggestion. I added in the source of the generated patch.

Alan (not verified)

11 months ago

If you're using a front-end proxy like Varnish, you can also accomplish this at that level and then you've got one less thing to worry about per-project.

That approach definitely will also work in preventing your .htaccess file from being overwritten. It doesn, however, have the downside of meaning that your .htaccess file will never receive any of the ongoing core updates.