First up, agree with the author. Now if I may, a rant.
It boggles the mind that we style bikeshed over CSS. Is it honestly, truly, and pragmatically worthwhile to spend all this engineering effort manipulating, culling, tree shaking, modularizing our CSS? Pre-load that, inline this, async-load that, and what have we got for it? A web full of wiggling content, slow CDNs, dozens of requests, and an experience of a mis-timed, slow loading web of bullshit.
Deliver less CSS in the first place, be pragmatic, load it as one file when the user hits the website, from the same server they got the HTML from. Bam, lightning fucking fast, like it was before we invented all these new problems.
The brief flash of your half-styled website is useless to me. Block rendering until the CSS is done. I don't enjoy looking at half an interlaced video frame, or 100px of a 200px image, just show me the finished product.
The content wiggles and asynchronous bullshit is what makes your website -feel- slow, even if you managed to trick Google into thinking it loaded quickly.
> worthwhile to spend all this engineering effort manipulating, culling, tree shaking, modularizing our CSS?
You don't have to ever think about this stuff if you use a component-driven frontend framework like Vue w/ Vite (or Webpack).
The general design: you use one global .css file for your shared styles (app.css) and then each component has their own inline CSS.
Vite will automatically extract each component JS and CSS in tiny files and only load them when you visit a page with the specific component.
This all happens automatically and you can cleanly organize your project by keeping the styles close to the view template and logic (often the same file has the template, <style> and <script> which each get extract automatically into bundles). The .css/.js filenames match the component name which makes debugging easier too in dev console.
HTTP2 handles loading 20-100 small files like nothing and with large SaaS apps usually only have tons of components that rarely get used, so no need to load them all for every page.
If you had to think about this stuff, manage the tooling, and customize then it would be overhead, but otherwise with frameworks it's pure benefit.
How is it a micro-optimization to only load content that you need?
Like I said for large SaaS products you might never see 50% of the components on random settings and other less used pages. Some users only load a few index pages the first few times they visit so making it load faster is always a win.
Frameworks handle way more complex stuff that this. It's not overhead.
The way SPAs work is very performant but are also poor for indexing. Critical Path CSS is mostly used in user facing sites which one wants to get indexed by Google. As page speed is one of the factors considered in SEO, and rightly, one tries to optimize that. Vue, React, etc. are poor at SEO as they do not even try to do solve the problem as they are used to build apps and not sites.
I see you haven't used these libraries in at least a few years, because they all have frameworks that work with SEO by server side rendering content. React has NextJS while Vue has NuxtJS.
What speed differentials are you seeing? Since they are server side I don't see any speed differences at all since they just transmit raw HTML and CSS, same as if you would write it by hand.
>* is what makes your website -feel- slow, even if you managed to trick Google into thinking it loaded quickly.*
I assume this is why a lot of pages do it: SEO over UX. If they cared that much about user experience the ads and tracking would be gone before worrying about CSS in this way.
Which I think is the correct way to handle a CSS file, but it's not render blocking if you dynamically load it in with JS. That is what Critical path CSS is combatting against, the "FUIC" or flash of unstyled content, which of course is because people are loading in CSS asynchronously. A problem we created for ourselves.
A lot of new approaches atomically separate the CSS and JS into dozens of smaller files that load in as the parts of the website that need them load in. Critial CSS is likely also a response to the issues that this approach creates.
As the article alludes to, they're ideas that makes total sense clinically, but falls short really quickly in practice.
An SPA has all the tools in their tooling chain necessary to minimize delivered CSS, yet they're often still slower than a static page web application with a 5000 line CSS file. The issue isn't the CSS rendering, it's in the timing of the resources and their delivery. If you load CSS in 12 different files, especially if you're doing it as the user interacts with the site, then the browser has to combine, reprocess and repaint every line of CSS it has been given so far, each time it gets new CSS. That's after waiting for the network request.
I know I'm talking against industry opinion, but I don't believe browsers are the right platform to be loading things on the fly as the user needs them. It's 50-300+ms absolute best case, plus processing time. It's going to feel bad.
This is the reason I love tailwind. There's no bloat, no headache of naming conflicts (in fact they completely did away with naming), no orphan css. CSS for a full blown website can be less than 5kb. It takes away the pain of thinking about CSS architecture, naming things, conflicts etc and you can just focus on styling.
I did not expect to like tailwind, and it still irks my past-self's approach to CSS. But I can't argue that as a wholistic approach to CSS, it is very capable and once you learn the lexicon, very fast to develop with. I really did enjoy it.
Tailwind goes against all old CSS best practices, so I was strongly against it. But I eventually tried it after reading everyone recommending it and it’s great.
It's also one of the very few frameworks that cares about accessibility by default in their examples: no buttons-as-links, no divs-as-lists, clear contrast everywhere.
Been developing websites and apps since the days when we are worrying about the y2k bug. Tailwind is my favorite thing. Almost everything that I disliked about CSS is solved by it. Once you really kick the tires, you become way more productive:
Naming is hard. No more naming.
Switching between your markup and your CSS added friction to your flow. No more friction.
Avoiding problems caused by overly aggressive rules / cascade/ whatever is mostly a thing of the past.
I could go on, but if it’s not obvious, I absolutely love it. And the Tailwind UI project is 100% money we’ll spent, too.
I feel the same way about Tailwind, but haven't taken the plunge on Tailwind UI. Though I kinda want to buy it just to support the team. Do you feel like it constrains you too much, or no?
I don’t use anything other than the design ideas. When I’m building a feature, the first thing I do is find a design pattern from Tailwind UI. For that, it’s been well worth the few hundred bucks.
Makes CSS faster to write in the short term, and harder to read in the long term. Also makes complex markup abysmally dense and unwieldy. I understand why people like it but I avoid it when I can. As a Svelte user, my markup is sacred and each component has an automatically scoped style tag so Tailwind is an anti-pattern in any non-trivial project.
> Assume that I know _nothing_ about Tailwind, sell it to me. I'd appreciate it. Thanks.
Tailwind removes (or wilfully ignores) the "cascading" from css. It breaks all good, old css design patterns on purpose - in favor of "widget-level" styling.
It also effectivly builds its own (extendable) style language via named classes, ending up somewhere between "semantic" and "micro-styling" - but allowing for refactoring as needed. It reduces CSS to an implementation detail.
For example, in "Agile Web Development With Rails 7"[1], a number of form fields are initially styled as:
It may be a longer read than you were looking for, in which case I’d say just try it on a small project next time you get the chance. It looks like it wouldn’t work, but it really does.
Aside from was being said, for me, at least when developing component based app, it the better choice IMHO, most css classes contains just one css rule, so you when reading your component class already can visualize how the component looks like. If you are developing a old school web, I would say it could be a pain if you mid project want to change the style of one element (because you are not using components) you need to find and replace every occurrence of the elements classes.
Also not to mention, you skip all the nonsense of the CSS in JS and the need of tools to extract the CSS from JS. Also other big win is that you can use with other projects not based on JS, I'm toying with a WASM app and use tailwind without issues, just run the tailwind bin on my app change (takes a second or less to generate the css file), the only downside here is the need of nodejs (you might be able to download the binary directly and run that, like I do on CI).
I hated the idea of Tailwind when I first saw it. Decided on a whim last year to give it a solid try after hearing others say "you just have to experience it for yourself". I tried it, and now it's literally painful going back to anything else. I can't explain it, it's just magical.
In my opinion, Tailwind is to CSS what jQuery was to Javascript. It's just the right level of abstraction that you need.
But you have to just try it for yourself and experience it. I'd recommend using it with VSCode's intellisense plugin.
How is a class with a single rule, that does exactly what it's supposed to, not "semantic?" What do you think "semantic" means, in this context?
Maybe you mean classes organized with a high level of abstraction that have multiple rules and are meant to be related to the document structure in a specific way?
Nothing stops you from layering BEM on top of tailwind via @apply - although I'm not sure if that's a good idea. Maybe if creating a full, custom, design-system using tailwind?
Although the Tailwind author hates @apply and says they shouldn't have put it in the tailwind in the first place (mainly because it's a hard feature to develop - i wouldn't be surprised it would be removed). I think going tailwind only is bad too because you loose many of the nice functions of CSS like theming/cascade.
So i think using utility classes/tailwind for base/layout and still using named classes where it makes sense (like common reusable components, hover hiearchies, transitions/animations) is most practical approach. And using BEM as convention for the named classes is not a bad idea - certainly better than no system.
> Although the Tailwind author hates @apply and says they shouldn't have put it in the tailwind in the first place (mainly because it's a hard feature to develop - i wouldn't be surprised it would be removed). I think going tailwind only is bad too because you loose many of the nice functions of CSS like theming/cascade.
I'd say that dropping the cascade, along with namespacing "classes" (through the build-step) is the main feature of tailwind. It's a departure from CSS - I don't think I'd recommend to mix and match.
I like CSS, but I also see how it's a complex tool that's often used poorly, even by experienced developers.
As for themeing - I'd say that is well supported within tailwind.
Typical basic example where tailwind fails is styling text and i don't mean styling html generated from user markdown. (that is given - impossible to do with tw)
When you have any text on page like a heading - tailwind approach is for you to set ".text-xl .leading-tight .tracking-tight" ok fine. Your designer is then like. "Yeah on mobile the fontsize must be much smaller and thus tracking+leading bigger" (because these 3 are tightly related) then he will also want the heading even bigger on big screens (and decrease tracking+leading). Suddenly you are juggling 9 classes that also have some fixed value. So you look into Figma and see that on mobile the leading of this style of heading is 1.27. On laptops its 1.14 and on big screen it's 1.035. So what now - will you change the design or will you add new utility classes specifically for this heading?
So you realize the best approach is to create one class .text-heading-1 and use css - that solves all the issues and it's super easy to change moving forward when you need some fine grained responsive adjustment.
The great thing about TW is that it's very fine grained in html but on the other hand it's bad at being explicit and precise like css is. I think the reason people like TW so much is that huge chunk of web work is layouting. Flex here, justify there and add gap. No more pain to target/name element just because you need to add margin and flex.
Theming is OK to some point (like making dark version) but if you need to make broader changes between multiple themes it just crumbles.
> Suddenly you are juggling 9 classes that also have some fixed value
In my experience, devs using tailwind will happily wait until there are 20+ classes before refactoring - on the surface this seems insane - but if all you're editing is single, re-usable components (eg: "text on a page" is in a custom "article" component) - it's no longer quite as crazy.
> Theming is OK to some point (like making dark version) but if you need to make broader changes between multiple themes it just crumbles.
You wouldn't make css zen garden with tailwind - you would make such sweeping themes at component level, where you can change style, behavior (js) and markup all together.
Again - tailwind isn't for document styling, but for building applications - that happen to feature html and css as implementation details.
> Again - tailwind isn't for document styling, but for building applications - that happen to feature html and css as implementation details.
And yet it's app developers i know that hate it and don't want to use it (they already have single file components). But agencies making super custom content websites seem to be all over it. https://www.awwwards.com/websites/tailwind/ These websites are css zen gardens if anything.
I would say that'd be a Tailwind anti-pattern, since Tailwind exceeds by trying to do away with cascading, and BEM is an attempt to componentize CSS but retain some cascading.
Also, if you handed me a project with that approach, I would run for the hills! How would you decide when to use BEM styling and when to use the tailwind classes? It'd be a nightmare.
Use tailwind as much as possible until you feel like you need a class then use BEM for the naming/organisation. It's actually pretty practical system - with headwind the non TW classes sort to the front so you can immediately see if there is something more in css.
The resulting css you end up with is usually super light only with special things, edge cases, hover groups and stuff TW just doesn't handle.
If you ever use @apply,you need to come up with a name. I prefer a few @aplly classes to meaningless clutter of tailwind detailed styles.
You could of course have a policy to never refractor - but then you might need to enforce alphabetical use of tw classes to more easily keep styles in sync accross your codebase?
I am confused, are you for or against FOUC? Or for optimizing against it?
My core industry is e-commerce, have been doing it for over a decade. Our fastest sites are the fully cached, single CSS file sites. You get sent a small amount of CSS and HTML, and you are off to the races. Javascript comes later, and is not necessary to operate the site until checkout.
What kills a sites speed for us is usually shitty CDNs and bloated assets, we don't have those. There isn't a single e-commerce platform popular today that I feel does this well, but that is because they are all enterprise platforms full of enterprise features.
I have found https://github.com/addyosmani/critical-path-css-tools to be a great resource to master critical path CSS which improves page render speeds. It helps build fast rendering sites, sometimes even sub-second renders given you have a low latency backend.
> yet I find this post misleading. The examples shown are ways NOT to do frontend performance
Is advice different than what you prefer "misleading?"
Anyway, he doesn't actually weigh in on marking JS with `defer` or `async`. And the article is directly contesting preloading the styles, given his note on race conditions with `media` switching. Moving the CSS before the `</body>` closing tag is genuinely a way to really defer your CSS.
> Why would we ever put non-Critical CSS in the <head> in the first place?!
To this point from Harry, I find myself skeptical that anyone's really going to want to do that. Getting layout jank / cumulative layout shift fixed when the main stylesheet is applied is a sisyphean task.
I said misleading as the author mentions JavaScript preventing rendering which isn't the case for anybody who is looking to optimize their site for speed. There are other such assumptions. Performance engineer is most times going against the default way and standards which the author is aware which is why I am surprised with this post.
I have not run into layout janks when I have used critical path CSS. I'd look into other tools/settings while extracting the critical path CSS if this would happen to me.
> the author mentions JavaScript preventing rendering which isn't the case for anybody who is looking to optimize their site for speed
Rewriting your JS to be async-ready can be a huge lift. Not every site pursuing frontend performance is ready to do that, much less has done it. I think this article certainly has an audience.
> I have not run into layout janks when I have used critical path CSS
Call me a skeptic :) if a user is able to scroll beyond the fold before the CSS loads, you've introduced CLS. Spoiler: they will always be able to.
Critical CSS and 0 CLS happens only when you create skeleton placeholder components for everything below the fold as well. There are no "tool/settings" that will do this automatically.
> Rewriting your JS to be async-ready can be a huge lift. Not every site pursuing frontend performance is ready to do that, much less has done it. I think this article certainly has an audience.
CSSWizardry targets performance engineers which is why I pointed it out. I would never post that for some Next.js or other new hotness blogpost which isn't focussed on frontend performance.
> Call me a skeptic :) if a user is able to scroll beyond the fold before the CSS loads, you've introduced CLS. Spoiler: they will always be able to.
I can't speak on code I haven't worked on or predict how users will react on UIs without looking into data. But I read this as excuses from providing the best UX to the end user.
I don't think it's a typo - a sub-second render on a modern website with all the bells and whistles that users now expect...that probably puts you in the 99th percentile, maybe even higher.
Not disagreeing with you though - it's amazing how much bloat we've added. Nobody seems to get just how fucking fast the web actually is. Take a look at Figma or Linear if you want to see products that truly care about performance.
Your location decides the latency of the request. If you are close to server you can do less than 200ms time to first byte. Getting a server near your users help.
After that you have 500ms to render your webpage. If you are lazy loading images, and most assets below the fold, thats a lot of time to get a page rendered.
You may take a look at my personal website (https://www.troysk.com/) which is hosted via Cloudflare so has CDN support and does sub second render on first load even though its image heavy.
You have less CSS than HN, and hardly any JS. This doesn't add anything to the discussion of if this is easy or hard to do – your site will always have a fast FCP, even if you weren't serving with a CDN.
Note also that the parent commenter mentioned being logged in to HN. You're serving a static page, which will always be fast. There are many aspects of HN's site that cannot be cached for logged in users, which will mean a higher TTFB.
> If you are lazy loading images
> sub second render on first load even though its image heavy
Images are not render blocking, and so won't be a part of this discussion. (Nor would I consider 1MB of mostly SVGs to be "image heavy")
Your site actually has room for improvement on performance. You could likely cut your overall page weight in half (not that it would do much, being default fast).
> You have less CSS than HN, and hardly any JS. This doesn't add anything to the discussion of if this is easy or hard to do – your site will always have a fast FCP, even if you weren't serving with a CDN.
Many sites use static caching and then load dynamic parts asynchronously. The goal is to provide the end user a usable UI real fast. There is also Fastly and other new caching solutions if you are interested to know more.
> Your site actually has room for improvement on performance. You could likely cut your overall page weight in half (not that it would do much, being default fast).
How does it benefit? Have users reaction rates become faster than 1s on the web? This is a site not a game.
Setting aside that you're wrong (your style.css is 1.7kB, HN is 2.2kB), that small difference is kind of my point. Discussions of if a quick FCP is easy or not should be based on a typical site. Tiny, static portfolio sites ain't that.
> And hardly any JS? Can you even read code?
Your site's entire JS weight is below 50kB.
> How does it benefit? Have users reaction rates become faster than 1s on the web?
Right. That's why I said, "not that it would do much, being default fast"
Not sure what you're doing at this point. Are you trying to make the case that your site uses a lot of JS...? The site's entire JS package consists of LoadCSS, some ScrollMagic calls, and a Google Analytics tag.
It is easy with a little automation :). In the build process, adding a step to calculate critical path css helps. This should probably be the last step and run on production post deploy. The calculated CSS is stored in redis which will be appended in the head for subsequent requests. This tool had inspired me to build this process at multiple orgs I have worked with https://github.com/pocketjoso/penthouse.
Agree on the async JS part but I guess browser makers will solve it soon.
I'm astounded that we still don't have proper lazy loading of stylesheets in 2022. We have async and defer for scripts but nothing for css. There are a bunch of hacks that exist, but none of them work if you want to use a strict CSP. Even if you use it then you have to worry about a FOUT instead of automatically loading the stylesheet from the browser's cache.
Inlining critical CSS is madness for any project that is above a certain scale. Maybe you can do it for a single page, but in my experience the results are not worth the effort. You can make your site exceptionally fast without it.
Nowadays it's probably better to just prompt the browser to preload CSS early with a link preload header.
Any page complicated enough to need critical CSS extraction is probably also going to take 50-100ms to render and transfer to the user. Just send the headers before you render and the client will probably have finished downloading them by the time you're done.
If clients are on a crappy network, critical css is even worse of an idea because they'll scroll down and get a broken page for 5 seconds
I don’t find the HTTP headers approach useful. Headers only appear a few bytes before the HTML so if you keep the regular link tag right at the top of the file you’re good to go; no need to mess with headers.
Every HTML file should start with: doctype, html tag, head tag, charset meta tag, title, stylesheet link tag. A couple of those tags are even optional.
The header approach is easier to graft on to an existing system where the entire html document is generated in one go. Otherwise you have to deal with generating the response in chunks which can be nontrivial.
In my experience “critical styles” are necessary to load fonts efficiently if you load any, and it’s beneficial to load a few color styles at the same time. Other than that it’s better to load them as separate resources.
I’m not sure you know exactly what “critical CSS” refers to because it’s unrelated to font loading and “color styles.” It has to do with inlining the CSS required for content “above the fold” while loading the rest asynchronously.
Not necessarily just for content above the fold there is more than one way to split between critical and non-critical e.g. styles for initial page render vs those used on interaction (for a popup menu for example)
> You can decide to put whatever you want into the style tag, but "critical CSS" explicitly refers to above the fold.
The Web Dev article might decide to define Critical CSS as about the fold but there are at least three ways of looking at Critical CSS
- Styles needed for above the fold
- Styles needed to layout a skeleton, and main content to avoid layout shifts
- Styles needed to layout the whole page
Above the fold is an arbitrary definition because it varies by device with different styles being required for different viewport sizes (at least)
The approach of scanning the rendering the page to see what styles are needed then extracting them into inline styles might work for small unchanging sites but it just doesn't scale
The only way I've seen sites maintain Critical Styles over time is to have a strong design system / patter library so they know what styles are needed to render the page and then split the styles between those needed for first render, and those needed on user interaction
> Above the fold is an arbitrary definition because it varies by device
I mean, sure. But your critical path of CSS is never going to mean the whole page (unless it's a very short page). And skeleton components is objectively a completely different thing. Having definitions, even if "arbitrary," are important.
I didn't mention skeleton components, I said 'lay out a skeleton and main content'
I'm well aware of Addy and Jonas' work…
In my experience trying to just extract and maintain ATF styles is impractical at scale as there 1,000s of viewport sizes, and large sites often have 10,000 pages – I've seen many a team try it and abandon it for something more realistic
I've used a broad definition of Critical CSS for a long time and will continue to do so as the narrow definition is impractical at scale IMV
Inline styles make CSP go from trivial to pain if you gotta create nonces without adding much value—to the point where I have the Lighthouse test ignored. I've never seen this sort of optimization being worthwhile and I'm happy the article starts off with how to show it's not a bottleneck.
I recently converted several of my projects into critical CSS and now I’m outranking my competitors who still use old CSS. Say what you want but Google loves fast loading sites and gives a good rankings boost. You can whine about critical CSS or get it done and win.
> Say what you want but Google loves fast loading sites and gives a good rankings boost
If you're passing your Core Web Vitals scores, there is no further ranking signal.
Transcribed from a 2021 Q&A with Google engineers
> beyond that point [of a good threshold for all Core Web Vital metrics], you don't get additional boost for reaching it even better. Like if you have your LCP at two seconds and you get it all the way down to one second, um, we've kind of publicly stated that that will not increase your ranking
I do this for CSS and most JavaScript, except for the OpenPGP.js module, which is one order of magnitude more LOC than all my other JS templates put together.
I have found that keeping my code short and getting it all out in 1-3 HTTP request is the optimal performance strategy.
It boggles the mind that we style bikeshed over CSS. Is it honestly, truly, and pragmatically worthwhile to spend all this engineering effort manipulating, culling, tree shaking, modularizing our CSS? Pre-load that, inline this, async-load that, and what have we got for it? A web full of wiggling content, slow CDNs, dozens of requests, and an experience of a mis-timed, slow loading web of bullshit.
Deliver less CSS in the first place, be pragmatic, load it as one file when the user hits the website, from the same server they got the HTML from. Bam, lightning fucking fast, like it was before we invented all these new problems.
The brief flash of your half-styled website is useless to me. Block rendering until the CSS is done. I don't enjoy looking at half an interlaced video frame, or 100px of a 200px image, just show me the finished product.
The content wiggles and asynchronous bullshit is what makes your website -feel- slow, even if you managed to trick Google into thinking it loaded quickly.