Here’s a little direct product endorsement for ya: I literally use Buddy for deployment on all my projects.
Buddy isn’t just a deployment tool, we’ll get to that, but it’s something that Buddy does very well and definitely a reason you might look at picking it up yourself if you’re looking around for a reliable, high-quality deployment service.
Here’s my current setup:
CSS-Tricks is WordPress site.
The whole wp-content folder is a private repository on GitHub.
The hosting is on Flywheel, which gives me SFTP access to the server.
When I push to the Master branch, Buddy automatically deploys the changed files over SFTP. This is fast because the fact it’s only dealing with changed files.
The setup on Buddy for this is incredibly nice and simple and I’ve never once had any problems with it. You may want to look at zero-downtime deployments as well, where files are uploaded to a separate directory first and swapped out with the destination directories if the entire upload is successful.
And I don’t just use this setup for CSS-Tricks but all my sites that need this kind of deployment.
But like I said, Buddy isn’t just deployment. Buddy is all about pipelines. You (visually) configure a bunch of tasks that you want Buddy to do for you and the trigger that kicks it off. Pushing to Master is just one possible trigger, you can also kick them off manually or on a timer.
What tasks? Well, a common one would be running your tests. You know: Continuous Integration (CI) and Continuous Development (CD). You can tell Buddy to run whatever terminal commands you want (they’ll spin up a Docker container for you), so however you run tests and get output will work just fine.
You could have it shoot you an email, hit some other web service, or run a build process.
Here’s the actual tasks I run in my pipeline right now:
Upload the files over SFTP
Tell Cloudflare to purge all the cache on the site
Send a message to a particular channel on Slack (also do that on failure)
It’s so easy to set up it almost encourages doing more with your pipelines. I need to get some Cypress tests in there and I’d love to integrate an action to automatically optimize all images in the commits.
Zach Leatherman worked closely with Chris to figure out the font loading strategy for this very website you’re reading. Zach walks us through the design in this write-up and shares techniques that can be applied to other projects.
Spoiler alert: Font loading is a complex and important part of a project.
The really interesting part of this post is the way that Zach talks about changing the design based on what’s best for the codebase — or as Harry Roberts calls it, “normalising the design.” Is a user really going to notice the difference between font-weight: 400 and font-weight: 500? Well, if we can ditch a whole font file, then that could have a significant impact on performance which, in turn, improves the user experience.
I guess the conversation can instead be framed like this: Does the user experience of this font outweigh the user experience of a slightly faster website?
And this isn’t a criticism of the design at all! I think Zach shows us what a healthy relationship between designers and developers can look like: collaborating and making joint decisions based on the context and the problem at hand, rather than treating static mockups as the final, concrete source of truth.
I first heard of Flywheel through their product Local, which is a native app for working on WordPress sites. If you ask around for what people use for that kind of work, you’ll get all sorts of answers, but an awful lot of very strong recommendations for Local. I’ve become one of them! We ultimately did a sponsored post for Local, but that’s based on the fact that now 100% of my local WordPress development work is done using it and I’m very happy with it.
Now I’ve taken the next step and moved all my production sites to Flywheel hosting!
Full disclosure here, Flywheel is now a sponsor of CSS-Tricks. I’ve been wanting to work with them for a while. I’ve been out to visit them in Omaha! (👋 at Jamie, Christi, Karissa, and everybody I’ve worked with over there.) Part of our deal includes the hosting. But I was a paying customer and user of Flywheel before this on some sites, and my good experiences there are what made me want to get this sponsorship partnership cooking! There has been big recent news that Flywheel was acquired by WP Engine. I’m also a fan of WP Engine, being also a premium WordPress host that has done innovative things with hosting, so I’m optimistic that a real WordPress hosting powerhouse is being formed and I’ve got my sites in the right place.
Developing on Local is a breeze
It feels like a breath of fresh air to me, as running all the dev dependencies for WordPress has forever been a bit of a pain in the butt. Sometimes you have it going fine, but then something breaks in the most inscrutable possible way and it takes forever to get going again. Whatever, you know what I mean. At this point, I’ve been running Local for over a year and have had almost no issues with it.
There are all kinds of features worth checking out here. Here’s one that is very likely useful to bigger teams. Say you have a Flywheel account with a bunch of production sites on it. Then a new person starts working with you and they have their own computer. You connect Local to Flywheel, and you can pull down the site and have it ready to work on. That’s pretty sweet.
Local doesn’t lock you into anything either. You can use Local for local development and literally use nothing else. Local can push a site up to Flywheel hosting too, which I’ve found to be mighty useful particularly for that first deployment of a new site, but you don’t have to use that if you don’t want. I’ll cover more about workflow below.
Other features that I find worthy of note:
Spinning up a new site takes just a second. A quick walkthrough through a wizard where they ask you some login details but otherwise offer smart-but-customizable defaults.
Dealing with HTTPS locally is easy. It will create a certificate for you and trust it locally with one click.
You can flip on “Live Link”, which uses ngrok to create a live, sharable URL to your localhost site. Great for temporarily showing a client or co-worker something without having to move anything.
One click to pop open the database in Sequel Pro, my favorite free database tool. Much easier than trying to spin up phpMyAdmin or whatever on the web to manage from there.
Flywheel’s Dashboard is so clear
I love the simple UI of Local, and I really like how that same design and spirit carries over into the Flywheel hosting dashboard.
There are so many things the dashboard makes easy:
You need an SSL cert? Click some buttons.
Wanna force HTTPS? Flip a switch.
Wanna convert the site to Multisite? Hit a button.
Need to edit the database? There is a UI around it built in.
Want a CDN? Toggle a thing.
Need to invite a collaborator on a site? Go for it.
Need a backup? There are in there, download it or restore to that point.
It’s a big deal when everything is simple and works. It means you aren’t burning hours fighting with tools and you can use them doing work that pushes you forward.
When I set up my new CSS-Tricks workflow, I had Flywheel move the site for me (thanks gang!) (no special treatment either, they’ll do that for anybody).
I’ve got Local already, so my local development process is the same. But I needed to update my deployment workflow for the new hosting. Local can push a site up to Flywheel hosting, but it just zips everything up and sends it all up. Great for first deployment but not perfect for tiny little changes like 95% of the work I do. There is a new Local for Teams feature, which uses what they call MagicSync for deployment, which only deploys changed files. That’s very cool, but I like working with a Git-based system, where ultimately merges to master are what trigger deployment of the changed files.
For years I’ve used Beanstalk for Git-based deployment over SFTP. I still am using Beanstalk for many sites and think it’s a great choice, but Beanstalk has the limitation that the Git-repo is basically a private Git repo hosted by Beanstalk itself.
During this change, I needed to switch up what the root of the repo is (more on that in a second) so I wanted to create a new repo. I figured rather than doing that on Beanstalk, I’d make a private GitHub repo and set up deployment from there. There are services like DeployHQ and DeployBot that will work well for that, but I went with Buddy, which has a really really nice UI for managing all this stuff, and is capable of much more than just deployment should I ultimately need that.
Regarding the repo itself, one thing that I’ve always done with my WordPress sites is just make the repo the whole damn thing starting at the root. I think it’s just a legacy/comfort thing. I had some files at the root I wanted to deploy along with everything else and that seemed like the easiest way. In WordPress-land, this isn’t usually how it’s done. It’s more common to have the /wp-content/ folder be the root of the repo, as those are essentially the only files unique to your installation. I can imagine setups where even down to individual themes are repos and deployed alone.
I figured I’d get on board with a more scoped deployment, but also, I didn’t have much of a choice. Flywheel literally locks down all WordPress core files, so if your deployment system tries to override them, it will just fail. That actually sounds great to me. There is no reason anyone from the outside should alter those files, might as well totally remove it as an attack vector. Flywheel itself keeps the WordPress version up to date. So I made a new repo with /wp-content/ at the root, and I figured I’d make it on GitHub instead just because that’s such an obvious hub of developer activity and keeps my options wide open for deployment choices.
For the same kind of spiritual reasons, during the the move, I moved the DNS over to Cloudflare. This gives me control over DNS from a third-party so it’s easy for me to point things where I need them. Kind of a decentralization of concerns. That’s not for everyone, but it’s great for me on this project. While now I might suffer from Cloudflare outages (rare, but it literally just happened), I benefit from all sorts of additional security and performance that Cloudflare can provide.
So the workflow is Local > GitHub > Buddy > Flywheel.
And the hosting is Cloudflare > Flywheel with image assets on Cloudinary.
And I’ve got backups from both Flywheel and Jetpack/VaultPress.
The front-end conference website got some upgrades.
We keep a list of all conferences out there related to the topics we write about here on CSS-Tricks! All things front-end web design and development!
It’s a little 11ty site on Netlify, where you can contribute to anytime — particularly by adding conferences that fit the vibe that you know about.
Notably, every conference listed has a permalink (example). We did that so we could play around with dynamically generating images for them. It’s super basic right now, but it was fun to play with. Dynamic CMS data is fed into an SVG, then also converted to a PNG at build time. Fancy. My hope is to upgrade the CMS to allow for cool custom backgrounds for each conference and then use them in these generated graphics.
The trip from my house to work is a short bike ride, but it’s a fairly dramatic elevation change, and my big ass is not capable of hauling itself up there. It’s much easier with this, even with a couple of loaded saddlebags and a toddler on the back of it.
I almost wish our URLs had years in them because I still don’t have a way to scope analytic data to only show me data from content published this year. I can see the most popular stuff from the year, but that’s regardless of when it was published, and that’s dominated by the big guides we’ve had for years and keep updated.
I have been a long-time reader of CSS-Tricks, but have not yet had something to contribute with. Until now. Being a Google Analytics specialist by day, this was at last something I could contribute to CSS-Tricks. Let’s extend Google Analytics on CSS-Tricks!
Enter Google Analytics custom dimensions
Google Analytics gives you a lot of interesting insights about what visitors are doing on a website, just by adding the basic Google Analytics snippet to every page.
But Google Analytics is a one-size-fits-all tool.
In order to make it truly meaningful for a specific website like CSS-Tricks we can add additional meta information to our Google Analytics data.
The year an article was posted is an example of such meta data that Google Analytics does not have out of the box, but it’s something that is easily added to make the data much more useful. That’s where custom dimensions come in.
Create the custom dimension in Google Analytics
The first thing to do is create the new custom dimension. In the Google Analytics UI, click the gear icon, click Custom Definitions and then click Custom Dimensions.
This shows a list of any existing custom dimensions. Click the red button to create a new custom dimension.
Let’s give the custom dimension a descriptive name. In this case, “year” seems quite appropriate since that’s what we want to measure.
The scope is important because it defines how the meta data should be applied to the existing data. In this case, the article year is related to each article the user is viewing, so we need to set it to the “hit” scope.
Another example would be meta data about the entire session, like if the user is logged in, that would be saved in a session-scoped custom dimension.
Alright, let’s save our dimension.
If we had other custom dimensions defined, then those would live in another index. There is no way to change the index of a custom dimension, so take note of the one being used. A list of all indices can always be found in the overview:
That’s it, now it’s time to code!
Now we have to extract the article year in the code and add it to the payload so that it is sent to Google Analytics with the page view hit.
This is the code we need to execute, per the snippet we were provided when creating the custom dimension:
var dimensionValue = 'SOME_DIMENSION_VALUE'; ga('set', 'dimension1', dimensionValue);
Here is the tricky part. The ga() function is created when the Google Analytics snippet is loaded. In order to minimize the performance hit, it is placed at the bottom of the page on CSS-Tricks. This is what the basic Google Analytics snippet looks like:
We need to set the custom dimension value after the snippet is parsed and before the page view hit is sent to Google Analytics. Hence, we need to set it here:
// ... ga('create', 'UA-12345-1', 'auto'); ga('set', 'dimension1', dimensionValue); // Set the custom dimension value ga('send', 'pageview');
Save the article year within the loop
In WordPress, a standard loop starts here:
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
…and ends here:
<?php endwhile; else : ?> <p><?php esc_html_e( 'Sorry, no posts matched your criteria.' ); ?></p> <?php endif; ?>
<script> var articleYear = "<?php the_time('Y') ?>"; </script>
Reference the article year in the Google Analytics snippet
That’s it! The Google Analytics page view hit will now include the article year for all pages where it is defined.
Custom dimensions do not apply to historical data
One thing to know about custom dimension — or any other modifications to your Google Analytics data — is that they only apply to new data being collected from the website. The custom dimensions described in this article was implemented in January 2019, and that means if we look at data from 2018 it will not have any data for the custom dimensions.
This is important to keep in mind for the rest of this article, when we begin to look into the data. The custom dimensions are added to all posts on CSS-Tricks, going all the way back to 2007, but we are only looking at page views that happened in 2019 — after the custom dimensions was implemented. For example, when we look at articles from 2011, we are not looking at page views in 2011 — we are looking at page views of posts from 2011 in 2019. This is important to keep in mind, when we start to look at posts from previous years.
All set? OK, let’s take a look at the new data!
Viewing the data in Google Analytics
The easiest way to see the new data is to go to Behavior → Site Content → All Pages, which will show the most viewed pages:
In the dropdown above the table, select “year” as a secondary dimension.
That gives us a table like the one below, showing the year for all articles. Notice how the homepage, which is the second most viewed page, is removed from the table because it does not have a year associated with it.
We start to get a better understanding of the website. The most viewed page (by far) is the complete guide to Flexbox which was published back in 2013. Talk about evergreen content!
Secondary is good, primary is better
OK, so the above table adds some understanding of the most viewed pages, but let’s flip the dimensions so that year is the primary dimension. There is no standard report for viewing custom dimensions as the primary dimension, so we need to create a custom report.
Give the Custom Report a good name. Finding the metrics (blue) and dimensions (green) is easiest by searching.
Here is what the final Custom Report should look like, with some useful metrics and dimensions. Notice how we have selected Page below Year. This will become useful in a second.
Once we hit Save, we see the aggregate numbers for all article years. 2013 is still on top, but we now see that 2011 also had some great content, which was not in the top 10 lists we previously looked at. This suggests that no single article from 2011 stood out, but in total, 2011 had some great articles that still receive a lot of views in 2019.
The percentage next to the number of page views is the percentage of the total page views. Notice how 2018 “only” accounts for 8.11% of all page views and 2019 accounts for 6.24%. This is not surprising, but shows that CSS-Tricks is partly a huge success because of the vast amount of strong reference material posted over the years, which users keep referring to.
Let’s look into 2011.
Remember how we set up the Custom Report with the Page below the Year in dimensions? This means we can now click 2011 and drill-down into that year.
It looks like a lot of almanac pages were published in 2011, which in aggregate has a lot of page views. Notice the lower-right corner where it says “1-10 of 375.” This means that 375 articles from 2011 have been viewed on the site in 2019. That is impressive!
Back to the question: Great content from 2018
Before I forget: Let’s answer that initial question from Chris.
Let’s scope the analytics data to content published this year (2018). Here are the top 10 posts:
For the last few years, I’ve been trying to think of CSS-Tricks as this two-headed beast. One head is that we’re trying to produce long-lasting referential content. We want to be a site that you come to or land on to find answers to front-end questions. The other head is that we want to be able to be read like a magazine. Subscribe, pop by once a week, snag the RSS feed… whatever you like, we hope CSS-Tricks is interesting to read as a hobbyist magazine or industry rag.
Let’s dig into that with another custom dimension: Post type. CSS-Tricks uses a number of custom post types like videos, almanac entries, and snippets in addition to the built-in post types, like posts or pages.
Let’s also extract that, like we did with the article year:
<script> var articleYear = "<?php the_time('Y') ?>"; var articleType = "<?php get_post_type($ post->ID) ?>"; </script>
We’ll save it into custom dimension Index 2, which is hit-scoped just like we did with year. Now we can build a new custom report like this:
Now, blog posts can also be referential content, so it is safe to say that at least half of the traffic on CSS-Tricks is coming because of the referential content.
From a one-man band to a 333-author content team
When CSS-Tricks started in 2007 it was just Chris. At the time of writing, 333 authors have contributed.
Let’s see how those authors have contributed to the page views on CSS-Tricks using — you probably guessed it — another custom dimension!
<script> var articleYear = "<?php the_time('Y') ?>"; var articleAuthor = "<?php the_author() ?>"; var articleType = "<?php get_post_type($ post->ID) ?>"; </script>
Here are the top 10 most viewed authors in 2019.
Let’s break this down even further by year with a secondary dimension and select 500 rows in the lower-right corner, so we get all 465 rows.
We can then export the data to Excel and make a pivot table of the data, counting authors per year.
You like charts? We can make one with some beautiful v17 colors, showing the number of authors per year.
It is amazing to see the steady growth in authors contributing to CSS-Tricks per year. And given 2019 already has 33 different authors, it looks like 2019 could set a new record.
But are those new authors generating any page views?
Let’s make a new pivot chart where we compare Chris to all other authors.
…and then chart that over time.
It definitely looks like CSS-Tricks is becoming a multi-author site. While Chris is still the #1 author, it is good to see that the constant flow of new high-quality content does not solely depend on him, which is a good trend for CSS-Tricks and makes it possible to cover a lot more topics going forward.
But what happened in 2011, you might ask? Let’s have a look. In a custom report, you can have five levels of dimensions. For now we will stick with four.
Now we can click on the year 2011 and get the list of authors.
Hello Sara Cope! What awesome content did you write in 2011?
Looks like a lot of those almanac pages we saw earlier. Click that!
Indeed, a lot of almanac pages! 107 to be exact. A lot of great content that still receives lots of page views in 2019 to boot.
Google Analytics is a powerful tool to understand what users are doing on your website, and with a little work, meta data that is specific to your website can make it extra powerful. As seen in this article, adding a few simple meta data that’s already accessible in WordPress can unlock a world of opportunities to analyze and add a whole new dimension of knowledge about the content and visitors of a site, like we did here on CSS-Tricks.