Get that desk more cuter, fam. Amy (@sailorhg) has this perfectly cute minisite with assorted desktop backgrounds, fonts, editor themes, keyboard stuff, and other accessories. These rainbow cables are great.
I updated my personal website the other day. Always a fun project since it’s one of the few where it’s 100% just me. It’s my own personal playground with no other goal than making the site represent me to have a little fun. It’s not a complete re-write, just some new paint.
I thought I’d document little bits of it here just to hone in on some of the bits of trickery in the spirit of learning through sharing.
Hoefler Fonts
I think the Inkwell family is super cool. I like mix and matching not just the weights but the serif and sans-serif and caps vs not.
I used Inkwell in the last design as well, but I was worried that it was a little too jokey for blog post body copy. My writing is extremely casual, but not always, and Inkwell is way too jovial for serious topics. I went with Ideal Sans for body copy last time, but the pairing with Inkwell felt a little off.
This time I went with Whitney for general body copy, which is still pretty lighthearted, but works when the copy is more straight toned.
Blogroll
If you’re going to zebra-stripe a table, you’d do something like…
What if you wanted to rotate four colors though? It’s still :nth-child trickery, selecting every four, and then offsetting. Here, I’ll do it with list items in Sass (the nesting is nice, not having to repeat the selector):
li {
&:nth-child(4n) a {
color: $blue;
}
&:nth-child(4n + 1) a {
color: $yellow;
}
&:nth-child(4n + 2) a {
color: $red;
}
&:nth-child(4n + 3) a {
color: $purple;
}
}
That’s what I did to build the colorized blogroll:
Note the Sass used above… I used Sass because it was already in use on the project. All I had to do was open CodeKit and the processing was ready-to-go.
Oh, and blogrolls are cool again.
Chill YouTube
I used this click-to-load-YouTube-(at all) technique which is still extremely clever. Having an <iframe> that behaves just like a YouTube embed would but only loading a simple static image (rather than heaps and heaps of resources) is great for performance and behaves essentially the same as a normal YouTube embed does.
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/Y8Wp3dafaMQ"
srcdoc="<style>*{padding:0;margin:0;overflow:hidden}html,body{height:100%}img,span{position:absolute;width:100%;top:0;bottom:0;margin:auto}span{height:1.5em;text-align:center;font:48px/1.5 sans-serif;color:white;text-shadow:0 0 0.5em black}</style><a href=https://www.youtube.com/embed/Y8Wp3dafaMQ?autoplay=1><img src=https://img.youtube.com/vi/Y8Wp3dafaMQ/hqdefault.jpg alt='Video The Dark Knight Rises: What Went Wrong? – Wisecrack Edition'><span>▶</span></a>"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
title="The Dark Knight Rises: What Went Wrong? – Wisecrack Edition"
></iframe>
Custom Post Types everywhere
I’m a big fan of giving myself structured data to work with. In WordPress-land, that often means Custom Post Types paired with something like the Advanced Custom Fields plugin for just the right data needed for the job.
Then I can loop over stuff and output it however I want. This isn’t that fancy, but it opens up whatever future doors I want to a lot easier.
Build your own bio
There is nothing fancy about how this works:
I literally made 18 <div> elements (3 lengths * 2 styles * 3 code types = 18) and swap between with a bit of JavaScript that calculates a class string based on the current choices, selects that class, and unhides it while hiding the rest.
$(".bio-choices input").on("change", function () {
var lengthClass = ".bio-" + $("input[name=length]:checked").attr("id");
var styleClass = ".bio-" + $("input[name=style]:checked").attr("id");
var codeClass = ".bio-" + $("input[name=code]:checked").attr("id");
var selector = lengthClass + styleClass + codeClass;
$(".bio").hide();
$(selector).show();
});
jQuery! That’s what was already on the site, and the site also uses the jQuery version of FitVids for responsive videos — so I figured I’d just leave it all be.
If I was going to re-write these bits of the site, I’d probably rip out jQuery and use this for FitVids. Then I’d find a way to only have three bios (maybe six if I can’t find a nice way to handle first vs. third person with word swaps) and then get the rest by automatically converting the formats somehow (maybe some cloud function if I had to).
ztext.js
I used ztext for the header! It’s this kinda stuff that makes the web feel extra webby to me. I’m not sure I’d do something with quite so much movement on a site like CSS-Tricks (because people visit it more often and the time-on-site is higher). But for a site that people might land on once in a blue moon, it has the right amount of cheerful levity, I think.
Background SVG
I was stoked to see the SVG Backgrounds site get an upgrade lately. I was playing around in there and was like YES, I’m doing this.
I went with a background-attachment: fixed look, which I think I like. I also added the slideout footer effect on desktop, but I’m less sold that it’s working here. It’s more fun when the background changes, and that doesn’t happen here. I’ll probably either change the background of the footer, or rip the effect out.
Filter trick for links
Some of the different sections on the site use a different primary highlight color, and I’m having the links in those sections follow that color. That might be questionable (perhaps all links should be blue) but, so far, I think it makes decent sense (they still have hover and focus styles). When you have a variety of colors and styles for interactive elements though, it often means that you have to create special alternate styles for hover and focus. That could mean crafting bespoke color alterations for each color. Not the end of the world, but I really like this little trick for interactive styles that ends up with a consistent look across all colors:
Anyway! This was just a couple hours of paint on this site. Mostly because blogrolls were the CodePen Challenge that week. But I can never touch a site I haven’t in a while and just do one thing. I get sucked in and gotta do more!
Twenty-plus years ago, tables were the main way web pages were created in HTML. It gave web builders consistent control of constructing pages with some “design.” No longer did sites only have to be top-to-bottom in a linear manner — they could be set up with columns that align left-to-right and top-to-bottom. Back then, it was seen as a huge breakthrough.
Tables, however, were never designed to lay out pages and, in fact, have all sorts of problems when used that way today. It was a convenient hack, but at the time, a very welcome one, particularly for those trying to achieve a super-specific layout that previous ways couldn’t handle.
Fast-forward to modern days and it’s now obvious that were tons of issues with the table layout approach. Accessibility is a big one.<table>, <th>, <tr> and <td> elements aren’t exactly accessible, especially when they’re nested several levels deep. Screen readers — the devices that read web content and serve as a measure of accessibility compliance — struggle to parse them into cohesive blocks of content. That’s not to say tables are bad; they simply were never intended as a layout mechanism.
Check out this table layout. Feel free to run it through VoiceOver or whatever screen reading software you have access to.
Yes, that example looks very much like a typical website layout, but it’s crafted solely with a table. You can see how quickly it becomes bloated and inaccessible the very moment we start using it for anything other than tabular data.
So after more than 20 years of being put through the ringer, you might think we should avoid tables altogether. If you’ve never shipped a table-based layout, you’ve undoubtedly heard war stories from those of us who have, and those stories are never kind. It’s like we’ve sort of made tables the “Internet Explorer of HTML elements.”
But that’s not totally fair because tables do indeed fill a purpose on the web and they are indeed accessible when they are used correctly.
Tables are designed to handle data that is semantically related and is best presented in a linear-like format. So, yes, we can use tables today in the year 2020, and that will likely continue to be true many years from now.
Here’s a table being used to display exactly what it’s intended to: tabular data!
With the push toward web standards in the early 2000s, tables were pushed aside as a layout solution in favor of other approaches, most notably the CSS float property. Designers and developers alike rejoiced because, for the first time, we had a true separation of concerns that let markup do the markup-y things it needs to do, and CSS to do the visual stuff it needs to do. That made code both cleaner and way easier to maintain and, as a result, we could actually focus on true standards, like accessibility, and even other practices, like SEO.
See (or rather hear) the difference in this example?
Many of us have worked with floats in the past. They were originally designed to allow content to flow around images that are floated either to the left or right, and still be in the document flow. Now that we’ve gotten newer layout features — again, like grid and flexbox — floats, too, have sort of fallen by the wayside, perhaps either because there are better ways to accomplish what they do, or because they also got the same bad rap as tables after being (ab)used for a long time.
But floats are still useful and relevant! In fact, we have to use them for the shape-outside property to work.
A legitimate float use case could be for wrapping content around a styled <blockquote>.
CSS features like grid, flexbox, and multicolumn layouts are among the wonderful tools we have to work with these days. With even more layout possibilities, cleaner and more accessible code, they will remain our go-to layout approaches for many years to come.
No hacks or extra code in this flexbox example of the same layout we’ve looked at throughout this article:
So, next time you find yourself considering tables or floats, reach for them with confidence! Well, when you know the situation aligns with their intended use. It’s not like I’m expecting you to walk away from this with a reinvigorated enthusiasm for tables and floats; only that, when used correctly, they are perfectly valid techniques, and even continue to be indispensable parts of our overall toolset.
Link building campaigns shouldn't have a start-and-stop date — they should be ongoing, continuing to earn you links over time. In this informative and enduringly relevant 2018 edition of Whiteboard Friday, guest host Paddy Moogan shares strategies to achieve sustainable link building, the kind that makes your content efforts lucrative far beyond your initial campaigns for them.
Click on the whiteboard image above to open a high-resolution version in a new tab!
Video Transcription
Hi, Moz fans. Welcome to Whiteboard Friday. I'm not Rand. I'm Paddy Moogan. I'm the cofounder of Aira. We're an agency in the UK, focusing on SEO, link building, and content marketing. You may have seen me write on the Moz Blog before, usually about link building. You may have read my link building book. If you have, thank you. Today, I'm going to talk about link building again. It's a topic I love, and I want to share some ideas around what I'm calling "sustainable link building."
Problems
Now, there are a few problems with link building that make it quite risky, and I want to talk about some problems first before giving you some potential solutions that help make your link building less risky. So a few problems first:
I. Content-driven link building is risky.
The problem with content-driven link building is that you're producing some content and you don't really know if it's going to work or not. It's quite risky, and you don't actually know for sure that you're going to get links.
II. A great content idea may not be a great content idea that gets links.
There's a massive difference between a great idea for content and a great idea that will get links. Knowing that difference is really, really important. So we're going to talk a little bit about how we can work that out.
III. It's a big investment of time and budget.
Producing content, particularly visual content, doing design and development takes time. It can take freelancers. It can take designers and developers. So it's a big investment of time and budget. If you're going to put time and budget into a marketing campaign, you want to know it's probably going to work and not be too risky.
IV. Think of link building as campaign-led: it starts & stops.
So you do a link building campaign, and then you stop and start a new one. I want to get away from that idea. I want to talk about the idea of treating link building as the ongoing activity and not treating it as a campaign that has a start date and a finish date and you forget about it and move on to the next one. So I'm going to talk a little bit about that as well.
Solutions
So those are some of the problems that we've got with content-driven link-building. I want to talk about some solutions of how to offset the risk of content-driven link building and how to increase the chances that you're actually going to get links and your campaign isn't going to fail and not work out for you.
I. Don't tie content to specific dates or events
So the first one, now, when you coming up with content ideas, it's really easy to tie content ideas into events or days of the year. If there are things going on in your client's industry that are quite important, current festivals and things like that, it's a great way of hooking a piece of content into an event. Now, the problem with that is if you produce a piece of content around a certain date and then that date passes and the content hasn't worked, then you're kind of stuck with a piece of content that is no longer relevant.
So an example here of what we've done at Aira, there's a client where they launch a piece of content around the Internet of Things Day. It turns out there's a day celebrating the Internet of Things, which is actually April 9th this year. Now, we produced a piece of content for them around the Internet of Things and its growth in the world and the impact it's having on the world. But importantly, we didn't tie it exactly to that date. So the piece itself didn't mention the date, but we launched it around that time and that outreach talked about Internet of Things Day. So the outreach focused on the date and the event, but the content piece itself didn't. What that meant was, after July 9th, we could still promote that piece of content because it was still relevant. It wasn't tied in with that exact date.
So it means that we're not gambling on a specific event or a specific date. If we get to July 9th and we've got no links, it obviously matters, but we can keep going. We can keep pushing that piece of content. So, by all means, produce content tied into dates and events, but try not to include that too much in the content piece itself and tie yourself to it.
II. Look for datasets which give you multiple angles for outreach
Number two, lots of content ideas can lead from data. So you can get a dataset and produce content ideas off the back of the data, but produce angles and stories using data. Now, that can be quite risky because you don't always know if data is going to give you a story or an angle until you've gone into it. So something we try and do at Aira when trying to produce content around data is from actually different angles you can use from that data.
So, for example:
Locations. Can you pitch a piece of content into different locations throughout the US or the UK so you can go after the local newspapers, local magazines for different areas of the country using different data points?
Demographics. Can you target different demographics? Can you target females, males, young people, old people? Can you slice the data in different ways to approach different demographics, which will give you multiple ways of actually outreaching that content?
Years. Is it updated every year? So it's 2018 at the moment. Is there a piece of data that will be updated in 2019? If there is and it's like a recurring annual thing where the data is updated, you can redo the content next year. So you can launch a piece of content now. When the data gets updated next year, plug the new data into it and relaunch it. So you're not having to rebuild a piece of a content every single time. You can use old content and then update the data afterwards.
III. Build up a bank of link-worthy content
Number three, now this is something which is working really, really well for us at the moment, something I wanted to share with you. This comes back to the idea of not treating link building as a start and stop campaign. You need to build up a bank of link-worthy content on your client websites or on your own websites. Try and build up content that's link worthy and not just have content as a one-off piece of work. What you can do with that is outreach over and over and over again.
We tend to think of the content process as something like this. You come up with your ideas. You do the design, then you do the outreach, and then you stop. In reality, what you should be doing is actually going back to the start and redoing this over and over again for the same piece of content.
What you end up with is multiple pieces of content on your client's website that are all getting links consistently. You're not just focusing on one, then moving past it, and then working on the next one. You can have this nice big bank of content there getting links for you all the time, rather than forgetting about it and moving on to the next one.
IV. Learn what content formats work for you
Number four, again, this is something that's worked really well for us recently. Because we're an agency, we work with lots of different clients, different industries and produce lots and lots of content, what we've done recently is try to work out what content formats are working the best for us. Which formats get the best results for our clients? The way we did this was a very, very simple chart showing how easy something was versus how hard it was, and then wherever it was a fail in terms of the links and the coverage, or wherever it was a really big win in terms of links and coverage and traffic for the client.
Now, what you may find when you do this is certain content formats fit within this grid. So, for example, you may find that doing data viz is actually really, really hard, but it gets you lots and lots of links, whereas you might find that producing maps and visuals around that kind of data is actually really hard but isn't very successful.
Identifying these content formats and knowing what works and doesn't work can then feed into your future content campaign. So when you're working for a client, you can confidently say, "Well, actually, we know that interactives aren't too difficult for us to build because we've got a good dev team, and they really likely to get links because we've done loads of them before and actually seen lots of successes from them." Whereas if you come up with an idea for a map that you know is actually really, really hard to do and actually might lead to a big fail, then that's not going to be so good, but you can say to a client, "Look, from our experience, we can see maps don't work very well. So let's try and do something else."
That's it in terms of tips and solutions for trying to make your link building more sustainable. I'd love to hear your comments and your feedback below. So if you've got any questions, anything you're not sure about, let me know. If you see it's working for your clients or not working, I'd love to hear that as well. Thank you.
Sign up for The Moz Top 10, a semimonthly mailer updating you on the top ten hottest pieces of SEO news, tips, and rad links uncovered by the Moz team. Think of it as your exclusive digest of stuff you don't have time to hunt down but want to read!
from The Moz Blog https://ift.tt/3jDED4l
via IFTTT
Maybe you’ve already heard of (or even worked with!) KendoReact. It’s popped up in some of my day-to-day conversations, especially those about working with design systems and React. You could think of it as a component library like Bootstrap or Material Design, except the components in KendoReact are far more robust. These are interactive, state-driven components ready to start building full-blown UI’s right out of the gate (not to mention, if you want to use Bootstrap as the theme, you absolutely can).
Whenever you’re thinking about using a UI library, you need to think about the styling capabilities. Are you able to really express your brand with these? Were they meant to be styled? What is the styling experience going to be like?
Fortunately, KendoReact really makes styling a citizen of the entire UI library.
KendoReact is a collection of UI components for building sites. It’s a pretty massive one. Over 80 by my count, and that doesn’t include the child components of heavy lifters like the <Grid /> family.
Here’s one, the <DropDownList />, and just using the default theme (even that is optional):
If I want to style this, I don’t need any special proprietary skills, I can just use CSS. Here’s me forcing a whole new look onto it with different colors and fonts, with just some simple CSS:
But hey, maybe you want to do something a bit more systematized than cowboying some random override CSS. I don’t blame you. Good news: KendoReact themes are Sass-powered. So you can control a lot of the colorization and styling just by changing a few Sass variables.
They have a whole theme builder you can use right on their site that spits out exactly what you need. Say you want to start from their base theme and go from there, select the Default theme:
Then you can play with all the colors in the UI to your liking. Here’s me poking at a theme with some CSS-Tricks colors.
I can download that from the site which will give me the variables as a SCSS file that I can apply before the default theme in my build (there is a great tutorial covering how to do that over on the Telerik blog). Plus, it gives me the whole dang CSS file of the theme if I want to use it that way, which is simple and quick. Here’s me using their conversational chat widget with that theme:
Again, I can start with Bootstrap, I can start with Material, I can start with their default theme, or I can start from scratch. Styling is totally up to me. Each theme has its perks and, as you might expect, are super flexible as far as configuring colors, fonts, and other design elements.
If you really get into this, of course you’ll be consulting their docs and finding your way around there (it’s nice to know they have really comprehensive docs). It’s all pretty straightforward though, you’ll do great! If you need to get going building out a state-driven interactive interface quickly without sacrificing any customizability or power, you’ll find KendoReact is your friend.
Back in July 2020, I got an email from James0x57 (I always try to refer to people by their name, but I think I get the sense they prefer to go by screen name) that says:
The entire world of branching conditional logic and bulk feature toggling for custom CSS properties is possible and only exists because of a tiny footnote in the CSS spec that has gone unnoticed.
Note: While <declaration-value> must represent at least one token, that one token may be whitespace.
In other words, --foo: ; is valid.
If you’re like me, this doesn’t read as some massive revelation that unlocks huge doors, but to smarter people, like James0x57, it does! We started working on a draft blog post, but for various reasons it didn’t make it all the way here. One of those reasons is that I just wasn’t getting it. Call me dense, sorry James0x57. One demo they sent me when I asked super dumbed-down example was helpful though, and I think it’s kind of clicked for me. Here’s my interpretation:
Let me attempt to explain:
The breakpoint we’ve set up here is a 900px max-width media query. You can see that’s where the variable --mq-sm flops from initial to an empty space value.
When the browser window is wider than 900px, that the value of --mq-sm is initial.
That makes the variable --padding-when-small contain two values — initial and 2rem —which, I guess is invalid.
So when we actually set the padding and call that variable like padding: var(--padding-when-small, var(--padding-when-large)), the second value (the “fallback”) is used because the first value is invalid.
When the browser window is narrower than 900px, the --mq-sm value is a space.
That makes the variable --padding-when-small value "(space)2rem" which, I guess is valid.
That means when we actually set the padding and call that variable like padding: var(--padding-when-small, var(--padding-when-large)), the first value is used.
So, now we can flip the padding between two values by changing a placeholder variable.
That clicks for me.
When see this as simply changing a single value, it’s almost like uh, ok, you’ve found a really complex way to change some padding, but you could have just changed the padding in the media query. But the trick is that now we have this placeholder variable that has changed and we can key into that to change unlimited other values.
We could have a single media query (or set of media queries) in our CSS that only toggles these placeholder variables and we use elsewhere to toggle values. That could be nice and clean compared to sprinkling media queries all over the CSS. It’s a proper toggle in CSS, like a form of IF/THEN logic that we haven’t quite had before.
James0x57 extended that thinking to all the logical possibilities, like AND, OR, XOR, NAND, NOR, and XNOR, but that lost me again. Not really a computer scientist over here. But you can follow their work if you want to see real world usage of this stuff.
This variable stuff is wild and gets very confusing. I noted in a possibly recent (but the byline says 2015?) article from Patrick Brosset that covers some tricky CSS custom properties stuff. For example, fallbacks can be infinitely nested, like:
Also, valid values for CSS custom properties can have commas in them like this:
content: var(--foo, one, two, three);
Is that really just one fallback with a single one, two, three value? This is rather mind-bending.
Anyway, fast-forward a bunch of months now, and CSS trickery master Lea Verou has set her sights on this whitespace-in-custom-properties stuff:
What if I told you you could use a single property value to turn multiple different values on and off across multiple different properties and even across multiple CSS rules?
It’s the same trick! In Lea’s example, though, she uses this ability to:
set variations on a button, and
set four different properties rather than one.
This really hones in on why this is the concept is so cool.
Lea points to some downsides:
There is no way to say “the background should be red if --foo is set and white otherwise”. Some such conditionals can be emulated with clever use of appending, but not most.
And of course there’s a certain readability issue: --foo: ; looks like a mistake and --foo: initial looks pretty weird, unless you’re aware of this technique.
We’re certainly entering the next era of how custom properties are used. First, we used them like preprocessor variables. Then we started seeing more cascade and fallback usage. Next, we used it alongside JavaScript more frequently. Now this.
There is even more writing about keeping CSS preprocessor variables around, not so much for the times when you only need what they can do, but for the things that only they can do, like having their color values manipulated.
Back in August 2020, when the content-visiblity property in CSS trickled its way into Chrome browsers, Una Kravets and Vladimir Levin wrote about it and we covered it. The weirdest part is that to get the performance value out of it, you pair it with contain-intrinsic-size on these big chunks of the page where you insert some arbitrary guess at a height. I wrote:
That part seems super weird to me. Just guess at a height? What if I’m wrong? Can I hurt performance? Can (or should) I change that value at different viewports if the height difference between small and large screens is drastic?
Jake Archibald and Das Surma just did a video on all this and it helped clarify that a bit. You can see at about 7:30 in just how confusing it is. Jake used this massive HTML spec page as a demo, and made <section> wrappers around big chunks of HTML, and applied:
section {
content-visibility: auto; /* this is the thing that delays painting */
contain-intrinsic-size: 1px 5000px; /* this is the guess at the height of the content, and also saying width doesn't matter */
}
Apparently that 5000px isn’t the height of the element, it’s the size of the content of that element. I guess that matters because it will push that parent element taller by that number, unless the parent element overrides that with a height of its own. The magic comes from the fact that the browser will only paint¹ the first section (where it’s very likely the viewport isn’t over 5000px tall) and defer the painting on the rest. Sorta like lazy loading, but everything rather than media alone. It assumes the next section is 5000px tall, but once the top of it becomes visible, it will actually get painted and the correct height will be known. So assuming your page is just big ass blocks on top of each other, using an extremely large number should work fine there. Godspeed if your site is more complicated than that, I guess.
It’s a good video and you should watch it:
This is yet another thing where you have to inform the browser about your site so that it can Do Performance Good™. It is information that it can figure out by itself, but not until it has done things that have a performance cost. So you have to tell it up front, allowing it to avoid doing certain types of work. With responsive images, if we give images a srcset attribute with images and tell the browser in advance how big they are, including a sizes attribute with information about how our CSS behaves, it can do calculations ahead of time that only download the best possible image. Likewise, with the will-change property in CSS, we can tell the browser when we’re going to be doing movement ahead of time so it can pre-optimize for that in a way it couldn’t otherwise. It’s understandable, but a little tiresome. It’s like we need a stuff-you-need-to-know.manifest file to give browsers before it does anything else — only that would be an additional request!
The accessibility implications are important too. Steve Faulkner did a test applying content-visibility: auto to images and paragraphs:
The content is visually hidden, but in both JAWS and NVDA the hidden<img> is announced but the content of the <p> element is not. This has to do with how the img and the p element content are represented in the browser accessibility tree: The img is exposed in the accessibility tree with the alt text as the accessible name. The content of the p element is not present in the accessibility tree.
He notes that content hidden this way should not be available to screen readers, per the spec. I could see it going either way, like hide it all as if it was display: none, meaning none of it is in the accessibility tree. Or, leave it all in the accessibility tree. Right now it’s a tweener where you might see a bunch of stray images in the accessibility tree without any other context than their alt text. This is an interesting example of new tech going out with more rough edges than you might like to see.
Speaking of alt text, we all know those shouldn’t be empty when they represent important content that needs to be described to someone who can’t see them. They should be like paragraphs, says Dave:
I finally made the simplest of all connections: alt text is like a paragraph. Word pictures. Basic I know, but it helps me contextualize how to write goodalt text as well as source order of my code.
I don’t want to be overly negative here! The performance gains for setting up a long-scrolling page with content-visibility is huge and that’s awesome. Being able to inform the browser about what is OK not to paint in two lines of code is pretty nice.
I keep saying “paint” but I’m not sure if that’s really the right term or if it means something more specific. The spec says stuff like “allowing user agents to potentially omit large swathes of layout and rendering work until it becomes needed” (emphasis mine).
There are so manystatic site generators (SSGs). It’s overwhelming trying to decide where to start. While an abundance of helpful articles may help wade through the (popular) options, they don’t magically make the decision easy.
I’ve been on a quest to help make that decision easier. A colleague of mine built a static site generator evaluation cheatsheet. It provides a really nice snapshot across numerous popular SSG choices. What’s missing is how they actually perform in action.
One feature every static site generator has in common is that it takes input data, runs it through a templating engine, and outputs HTML files. We typically refer to this process as The Build.
There’s too much nuance, context, and variability needed to compare how various SSGs perform during the build process to display on a spreadsheet — and thus begins our test to benchmark build times against popular static site generators.
This isn’t just to determine which SSG is fastest. Hugo already has that reputation. I mean, they say it on their website — The world’s fastest framework for building websites — so it must be true!
This is an in-depth comparison of build times across multiple popular SSGs and, more importantly, to analyze why those build times look the way they do. Blindly choosing the fastest or discrediting the slowest would be a mistake. Let’s find out why.
The tests
The testing process is designed to start simple — with just a few popular SSGs and a simple data format. A foundation on which to expand to more SSGs and more nuanced data. For today, the test includes six popular SSG choices:
Each test used the following approach and conditions:
The data source for each build are Markdown files with a randomly-generated title (as frontmatter) and body (containing three paragraphs of content).
The content contains no images.
Tests are run in series on a single machine, making the actual values less relevant than the relative comparison among the lot.
The output is plain text on an HTML page, run through the default starter, following each SSG’s respective guide on getting started.
Each test is a cold run. Caches are cleared and Markdown files are regenerated for every test.
These tests are considered benchmark tests. They are using basic Markdown files and outputting unstyled HTML into the built output.
In other words, the output is technically a website that could be deployed to production, though it’s not really a real-world scenario. Instead, this provides a baseline comparison among these frameworks. The choices you make as a developer using one of these frameworks will adjust the build times in various ways (usually by slowing it down).
For example, one way in which this doesn’t represent the real-world is that we’re testing cold builds. In the real-world, if you have 10,000 Markdown files as your data source and are using Gatsby, you’re going to make use of Gatsby’s cache, which will greatly reduce the build times (by as much as half).
The same can be said for incremental builds, which are related to warm versus cold runs in that they only build the file that changed. We’re not testing the incremental approach in these tests (at this time).
The two tiers of static site generators
Before we do that, let’s first consider that there are really two tiers of static site generators. Let’s call them basic and advanced.
Basic generators (which are not basic under the hood) are essentially a command-line interface (CLI) that takes in data and outputs HTML, and can often be extended to process assets (which we’re not doing here).
Advanced generators offer something in addition to outputting a static site, such as server-side rendering, serverless functions, and framework integration. They tend to be configured to be more dynamic right out of the box.
I intentionally chose three of each type of generator in this test. Falling into the basic bucket would be Eleventy, Hugo, and Jekyll. The other three are based on a front-end framework and ship with various amounts of tooling. Gatsby and Next are built on React, while Nuxt is built atop Vue.
Basic generators
Advanced generators
Eleventy
Gatsby
Hugo
Next
Jekyll
Nuxt
My hypothesis
Let’s apply the scientific method to this approach because science is fun (and useful)!
My hypothesis is that if an SSG is advanced, then it will perform slower than a basic SSG. I believe the results will reflect that because advanced SSGs have more overhead than basic SSGs. Thus, it’s likely that we’re going to see both groups of generators — basic and advanced — bundled together, in the results with basic generators moving significantly quicker.
Let me expand on that hypothesis a bit.
Linear(ish) and fast
Hugo and Eleventy will fly with smaller datasets. They are (relatively) simple processes in Go and Node.js, respectively, and their build output will reflect that. While both SSG will slow down as the number of files grows, I expect them to remain at the top of the class, though Eleventy may be a little less linear at scale, simply because Go tends to be more performant than Node.
Slow, then fast, but still slow
The advanced, or framework-bound SSGs, will start out and appear slow. I suspect a single-file test to contain a significant difference — milliseconds for the basic ones, compared to several seconds for Gatsby, Next, and Nuxt.
The framework-based SSGs are each built using webpack, bringing a significant amount of overhead along with it, regardless of the amount of content they are processing. That’s the baggage we sign up for in using those tools (more on this later).
But, as we add thousands of files, I suspect we’ll see the gap between the buckets close, though the advanced SSG group will stay farther behind by some significant amount.
In the advanced SSG group, I expect Gatsby to be the fastest, only because it doesn’t have a server-side component to worry about — but that’s just a gut feeling. Next and Nuxt may have optimized this to the point where, if we’re not using that feature, it won’t affect build times. And I suspect Nuxt will beat out Next, only because there is a little less overhead with Vue, compared to React.
Jekyll: The odd child
Ruby is infamously slow. It’s gotten more performant over time, but I don’t expect it to scale with Node, and certainly not with Go. And yet, at the same time, it doesn’t have the baggage of a framework.
At first, I think we’ll see Jekyll as pretty speedy, perhaps even indistinguishable from Eleventy. But as we get to the thousands of files, the performance will take a hit. My gut feeling is that there may exist a point at which Jekyll becomes the slowest of all six. We’ll push up to the 100,000 mark to see for sure.
After many iterations of building out a foundation on which these tests could be run, I ended up with a series of 10 runs in three different datasets:
Base: A single file, to compare the base build times
Small sites: From 1 to 1024 files, doubling each to time (to make it easier to determine if the SSGs scaled linearly)
Large sites: From 1,000 to 64,000 files, double on each run. I originally wanted to go up to 128,000 files, but hit some bottlenecks with a few of the frameworks. 64,000 ended up being enough to produce an idea of how the players would scale with even larger sites.
Click or tap the images to view them larger.
Summarizing the results
A few results were surprising to me, while others were expected. Here are the high-level points:
As expected, Hugo was the fastest, regardless of size. What I didn’t expect is that it wasn’t even close to any other generator, even at base builds (nor was it linear, but more on that below.)
The basic and advanced groups of SSGs are quite obvious when looking at the results for small sites. That was expected, but it was surprising to see Next is faster than Jekyll at 32,000 files, and faster than both Eleventy and Jekyll at 64,000 files. Also surprising is that Jekyll performed faster than Eleventy at 64,000 files.
None of the SSGs scale linearly. Next was the closest. Hugo has the appearance of being linear, but only because it’s so much faster than the rest.
I figured Gatsby to be the fastest among the advanced frameworks, and suspected it would be the one to get closer to the basics. But Gatsbyturned out to be the slowest, producing the most dramatic curve.
While it wasn’t specifically mentioned in the hypothesis, the scale of differences was larger than I would have imagined. At one file, Hugo was approximately 170 times faster than Gatsby. But at 64,000 files, it was closer — about 25 times faster. That means that, while Hugo remains the fastest, it actually has the most dramatic exponential growth shape among the lot. It just looks linear because of the scale of the chart.
What does it all mean?
When I shared my results with the creators and maintainers of these SSGs, I generally received the same message. To paraphrase:
The generators that take more time to build do so because they are doing more. They are bringing more to the table for developers to work with, whereas the faster sites (i.e. the “basic” tools) focus their efforts largely in converting templates into HTML files.
I agree.
To sum it up: Scaling Jamstack sites is hard.
The challenges that will present themselves to you, Developer, as you scale a site will vary depending on the site you’re trying to build. That data isn’t captured here because it can’t be — every project is unique in some way.
What it really comes down to is your level of tolerance for waiting in exchange for developer experience.
For example, if you’re going to build a large, image-heavy site with Gatsby, you’re going to pay for it with build times, but you’re also given an immense network of plugins and a foundation on which to build a solid, organized, component-based website. Do the same with Jekyll, and it’s going to take a lot more effort to stay organized and efficient throughout the process, though your builds may run faster.
At work, I typically build sites with Gatsby (or Next, depending on the level of dynamic interactivity required). We’ve worked with the Gatsby framework to build a core on which we can rapidly build highly-customized, image-rich websites, packed with an abundance of components. Our builds become slower as the sites scale, but that’s when we get creative by implementing micro front-ends, offloading image processing, implementing content previews, along with many other optimizations.
On the side, I tend to prefer working with Eleventy. It’s usually just me writing code, and my needs are much simpler. (I like to think of myself as a good client for myself.) I feel I have more control over the output files, which makes it easier for me to get 💯s on client-side performance, and that’s important to me.
In the end, this isn’t only about what is fast or slow. It’s about what works best for you and how long you’re willing to wait.
Wrapping up
This is just the beginning! The goal of this effort was to create a foundation on which we can, together, benchmark relative build times across popular static site generators.
What ideas do you have? What holes can you poke in the process? What can we do to tighten up these tests? How can we make them more like real-world scenarios? Should we offload the processing to a dedicated machine?
These are the questions I’d love for you to help me answer. Let’s talk about it.
You would think that hiding content with CSS is a straightforward and solved problem, but there are multiple solutions, each one being unique.
Developers most commonly usedisplay: none to hide the content on the page. Unfortunately, this way of hiding content isn’t bulletproof because now that content is now “inaccessible” to screen readers. It’s tempting to use it, but especially in cases where something is only meant to be visually hidden, don’t reach for it.
The fact is that there are many ways to “hide” things in CSS, each with their pros and cons which greatly depend on how it’s being used. We’re going to review each technique here and cap things off with a summary that helps us decide which to use and when.
How to spot differences between the techniques
To see a difference between different ways of hiding content, we must introduce some metrics. Metrics that we’ll use to compare the methods. I decided to break that down by asking questions focused on four particular areas that affect layout, performance and accessibility:
Accessibility: Is the hidden content read by a screen reader?
Documentflow: Will the hidden element affect the document layout?
Rendering: Will the hidden element’s box model be rendered?
Event triggers: Does the element detect clicks or focus?
Now that we have our criteria out of the way, let’s compare the methods. Again, we’ll put everything together at the end in a way that we can use it as a reference for making decisions when hiding things in CSS.
Method 1: The display property
We kicked off this post with a caution about using display to hide content. And as we established, using it to hide an element means that the element is not generated at all. It’s in the DOM, but never actually rendered.
The element will still show in the markup, if you inspect the page you will be able to see the element. The box model will not generate nor appear on the page, which also applies to all its children.
And what’s more, if the element has any event listeners — say a click or hover — they won’t register at all. And as we’ve discussed already, all the content will be ignored by screen readers. Here, we have two visible buttons and one hidden with display: none. All three buttons have click events but only the two visible buttons will render and register the clicks.
Display is the only property that will affect image request firing. If an image tag (or parent element) has a display property set to none either through inline CSS or by selector, the image will be downloaded. On the other hand, if the image is applied with a background property, it won’t be downloaded.
This is the case because the parser hasn’t applied the CSS when an HTML document is parsed and it encounters an <img> tag. On the other hand, when we apply the image to an element with a background property, the image won’t be downloaded because the parser hasn’t applied the CSS where the image is called. This behavior is matched across all latest browsers. The only exception is IE 11, which will download images in both cases.
Metric
Result
Is the hidden content read by a screen reader?
❌
Will the hidden element affect the document layout?
❌
Will the hidden element’s box model be rendered?
❌
Does the element detect clicks or focus?
❌
Method 2: The visibility property
If an element’s visibility property is set to hidden, then the element is “visually hidden.” Being “visually hidden” sounds a lot like what display: none does, but it’s incredibly different in that the element is generated and rendered, but invisible. This means that the element’s box model is present, giving it dimensions that continue to occupy space on the screen even though it doesn’t appear to be there.
Imagine you’re wearing an invisible cloak that makes you invisible to others, but you are still able to bump into things. You’re physically there, even if you’re invisible to the human eye.
But that’s where the differences between “visually hidden” and “not displayed” end. In fact, elements hidden with visibility and display behave the same in terms of accessibility and event triggers. Invisible elements are inaccessible to screen readers and won’t register events, as we see in the following demo that’s exactly the same as the last example, but merely swaps display: none with visibility: hidden.
Metric
Result
Is the hidden content read by a screen reader?
❌
Will the hidden element affect the document layout?
✅
Will the hidden element’s box model be rendered?
✅
Does the element detect clicks or focus?
❌
Method 3: The opacity property
The opacity property only affects the visual aspect of the element. If we set an element’s opacity to zero, the element will be fully transparent. Again, it’s a lot like visibility: hidden where we’re draping an invisible cloak on the element where it’s invisible, but still physically present.
In other words, what we have is a hollow, transparent element that acts like any other element, only it’s invisible. Sounds a lot like the visibility method, right? The difference is that a fully transparent element is still accessible to a screen reader and can register events, like clicks, as we see in the following example.
Metric
Result
Is the hidden content read by a screen reader?
✅
Will the hidden element affect the document layout?
✅
Will the hidden element’s box model be rendered?
✅
Does the element detect clicks or focus?
✅
Method 4: The position property
Pushing an element off-screen with absolute positioning is another way developers often hide things. Using top and left, we can push the element so far off the screen that there’s no way it will ever be seen. It’s like hiding the cookie jar outside of the house so the kids (or maybe you!) can’t find them.
“Absolute” is the key word here. If we set position to absolute, an element is taken out of the document flow which is a way of saying it no longer adheres to its natural position in the DOM. In other words, the page doesn’t reserve any space for it, which knocks the element out of order visually, positioning it to it’s nearest positioned element if there is one, or the document root if nothing else.
We take advantage of absolute positioning by taking the “hidden” element out of the document flow and offsetting it toward the top-left with values of -9999px.
Will the hidden element affect the document layout?
❌
Will the hidden element’s box model be rendered?
✅
Does the element detect clicks or focus?
✅
If the hidden element contains focusable content, the page will scroll to the element when it is in focus, creating a sudden jump.
Method 5: The “visually hidden” class
So far, the position method is the closest we’ve seen to an accessibility-friendly way to hide things in CSS. But the problem with focusable content causing sudden page jumps isn’t great. Another approach to accessible hiding combines absolute positioning, the clip property and hidden overflow. Scott O’Hara blogged it back in 2017.
We need to remove the element from the document flow. The best way to do this is by using position: absolute. This will remove the element, but we won’t push it off the screen.
.visually-hidden {
position: absolute;
}
We can hide the element by setting the width and height property to zero. Unfortunately, that won’t work because some screen readers will ignore elements with zero width and height. What we can do is set it to the second-lowest value, 1px. That means the content will easily overflow the space, so we also need overflow: hidden to make sure it doesn’t visually spill over.
To hide that one-pixel square, we can use the CSS clipping property. It is perfect for this situation, as it doesn’t affect screen readers. The content is there but, again, is visually hidden. The thing to note is that clip was deprecated in favor of clip-path but is still needed if we need to support older versions of Internet Explorer.
Another piece of the “visually hidden” class puzzle is to address smushed off-screen accessible text, an oddity that removes white-spacing between words, causing them to be read aloud like one big string of words. For example, “Welcome back home” will be read out as “Welcomebackhome.”
A simple solution to this problem is to set the white-space: nowrap:
And, finally! The last thing to consider is to allow certain element with native focus and active sites to display when they are in focus, while continuing to prevent other elements, like paragraphs, from displaying. We can use the :not pseudo-selector for that.
Will the hidden element affect the document layout?
❌
Will the hidden element’s box model be rendered?
❌
Does the element detect clicks or focus?
✅
Honorable mentions
There are even more methods than the five we’ve covered. For example, the text-indent property can push text off the screen like the position method:
.hidden {
text-indent: -9999em;
}
Unfortunately, this approach doesn’t jive with RTL writing modes. That makes it less adaptable than other solutions we’ve covered.
Another method is using transform to scale or move the element out of the way. It works the same — visually only — like opacity.
.hidden {
transform: scale(0);
}
Let’s put everything together!
We got to a solution that will visually hide content but still be accessible. Then, should you stop using display: none? No, this is still the best way to hide an element completely (visually and accessibly).
That said, It is worth mentioning that if you want to achieve an opposite result — hide something from the screen reader, the aria-hidden="true" attribute will hide the content from screen readers, but not visually.
With that, here is a complete table that compares all of the approaches. Use it to guide your decisions on how to hide content next time you find yourself in that situation.
Metric
Display
Visibility
Opacity
Position
Accessible Way
Is the hidden content read by a screen reader?
❌
❌
✅
✅
✅
Will the hidden element affect the document layout?