Technology Solutions for Everyday Folks

Drupal Site Improvements and other Housekeeping

On the heels of (and riding the wave following) my recent migration of Drupal to a new server host, I decided it was well past time to finally address some things with my Drupal instance that were been bugging me for a long time. Of course, something like this often spirals into its own set of "other" things to fix for the greater good. Since I have a fresh staging site along with a dedicated staging branch in my theme's git repo, making (and tracking) changes between instances is now easier than it was.

Fixing Inconsistencies First

Style Wonkiness

Over time I've noticed weird little inconsistencies with the various views and templates that make up the public-facing parts of this site. Most are relatively minor CSS scope problems or view structure modifications. Fixing this was pretty straightforward once I had a bunch of tabs/views side by side to identify the differences. For the most part, this was a matter of verifying or applying the same wrapper structure and classes to fields in the views, but there were also several specificity changes in the CSS itself to better generalize some styles or, in other cases, make them more specific/constrained. Easy to fix, but tedious to identify.

Some of these inconsistencies, however, were more structural in nature and required modifications to the underlying theme templates. More about that toward the end of this post.

Archive View Paths

Something that's bugged me from early on in my Drupal adventure, and something I've promptly ignored out of frustration, has been the handling of the blog post archive path/URL. Long story short, a design decision I made while originally creating the instance (using a post URL structure with /YYYY-MM/) means I cannot use native Drupal functionality like contextual filtering with multiple arguments due to the separator, in this case the -. I'd have been better off to use a slash separator (e.g. /YYYY/MM/). Try as I might, I could not get the view to behave with multiple arguments without a slash separator.

For reference the blog archive (by month) has a /blog/archive/YYYYMM/ path, but posts themselves have a /blog/YYYY-MM/ path, and I want the root of the latter to use the same view as the former but it doesn't work due to the aforementioned separator issue. Had I known this originally, I'd have set up the views and structure differently to better support this with built-in Drupal functionality.

After Googling to see if I could find a solution, I determined that there was no "no-code" solution to this problem—the only way to make this behave would be to deep edit my theme, and for a vanity/convenience purpose it's just not worth it. I instead settled on a remarkably easy solution: Apache RewriteRule.

This magical little one-liner or regex does exactly what I need it to do and, when placed in advance of the Clean URL rewrites in the appropriate site.conf Apache config, doesn't affect any of Drupal's internal configuration (or redirect valid content):

RewriteRule ^/blog/(\d{4})-(\d{2})/?$ /blog/archive/$1$2 [R,L]

It splits apart the year and month in the path and redirects to the proper archive path without the separator. As an added bonus, the trailing /? means it works with or without the trailing slash.

New Improvements and Functionality

I wanted to upgrade the theme to new/current versions of Fontawesome (FA), so it was time to create a new kit and deal with a conversion. I've been a FA Pro user for several years and there are many new icons and variations available, some of which I wanted to use in the site. Fortunately they make it pretty easy to include "old" (e.g. v. 4.x) hooks to maintain some backward compatibility and not break everything. Ultimately, upgrading Fontawesome was way more trivial than I'd expected—lots of find/replace in templates and some trailing find actions in rendered source to identify any loose ends applied directly as styles within Drupal views. The most "difficult" part was changing the theme .yml to use the new kit and move away from the old CDN CSS include since it was a shift from a style include to a js include.

In the process of moving to FA 6, I refreshed some of the icons to alternate (mostly Duotone where useful) versions, and made a few other deliberate changes along the way.

I'd also been meaning to add a "read time" metric to the blog for a long time. The Drupal node read time module handles this addition beautifully, but also triggered the most drastic underlying change: modification of some theme template structures. I could expose the reading time field to the display fields for the content type(s) and various views without issue; however, some custom templates I've built don't know what to do with the field...which means the field doesn't show on render. Further, the icon I wanted to use for read time isn't yet supported by CSS pseudo-classes, which means I have to "hard code" the i into the template.

Deep Template Modifications

In the past I've done relatively "light" Twig template updates and modifications to make things work. This read time modification meant digging deeper, since I needed to actually create specific field definitions for the homepage view (a fairly out-of-the-box view with little customization).

The Easy Ones First

I started with the other content type and views (blog post, blog list, blog archive, etc.) since they had existing templates for the other icons and tag lists and I'd just edited them for the FA conversion. This made the new read time data show up just as I wanted, and all was good. I also found a few other issues and corrected them at the same time!

That Pesky Tag List Separator

Something I'd long overlooked but discovered in this process is that the blog node itself wasn't separating tags with a delimiter (in my case, a comma). This functionality works great in the views where I'd told them to use a comma separator but that's due to how tags are exposed to the view versus the node. So I took the opportunity to properly separate tags for consistency. I found this very succinct and helpful information on how to handle this exact situation, and I was very happy with its simplicity:

{% for item in items %}
  {% if loop.last %}
    {% set separator = '' %}
  {% else %}
    {% set separator = ', ' %}
  {% endif %}
  <div{{ item.attributes.addClass('field__item') }}>{{ item.content }}{{ separator }}</div>
{% endfor %}

What About That Homepage View?

The homepage view was a different creature. I didn't want (or need) to build out a new template, but I needed to do some customization for the 'metadata' fields on a post. I'd enabled twig debugging which is super helpful, but not for digging into these individual field declarations. Fortunately through Googling I discovered this post that made the template naming convention for fields clear. I was able to quickly modify the "default" views-view-field.html.twig template into the expected views-view-field--viewname--pagename--node_read_time.html.twig file name. When these fields are encountered in the specific view/page combination, they will use the format I specify, in this case:

<i class="fa-regular fa-book-open-reader" aria-label="Read time:" title="Read time:"></i>
{{ output -}}

Other Housekeeping

Since I was deep in the bowels of Drupal's inner workings, and since several years have passed, it was a good time to go through and clean up. I removed modules no longer necessary or relevant (in some cases now duplicate due to their inclusion in Drupal core or contributed modules). I also removed several views I'd created (all of which were disabled) as test views for the stuff mentioned above and/or other functionality I never rolled out.

Ship It!

All of this work was handled and tested out in the staging instance, as it should be. The styles and template bits are all managed via git in branches, so a push to production was a matter of a pull request and merge between the staging and production branches, and a simple git pull on the production site along with a Drupal cache purge. In the actual cutover to production, this was also the first step as it brought the refreshed styles and template support over before the actual module and view changes were made so nothing would appear broken.

I then added the node read time module and configured it on production.

I could have handled the content type and views changes by doing a 'full' migration (including the database) between staging and production, but since there were only four actual changes in total (content type and views), it was easier/simpler to use Drupal's built-in single-item configuration import for the affected bits and copy/paste their yml between sites. Worked beautifully, and I would totally recommend for such changes, but you have to make sure you have a record of the changes to ensure everything moves over.

A Once-Over to Verify

I'd done most of these changes over the course of a few hours in a weekend, so everything was pretty fresh in mind. I preserved browser tabs for the key changes on the staging site; once everything was ported over and the Drupal caches cleared I could compare staging and production side-by-side.

And with that I've made some meaningful and important "behind the scenes" changes in this new environment, and in the way one should be doing this sort of thing!