Delivering a State-Machine with CSS / Another approach to Angular SEO

CSS is the language developers love to hate. This is mostly because of the inconsistent syntax. But many fail to recognize the power of modern CSS. I’m here to give an example of CSS power that may very well blow your mind. As an additional bonus, this technique can be used to provide a better first load experience for Angular apps and can be a fix for its SEO problems that are often hard to fix without some sort of server setup.

Ladies and gentlemen, introducing :target

:target is a special pseudo selector in CSS that helps you change the style of something that is currently the target — the hash address in the URL matches its id. It sounds harder than it is so let me jump straight into an example, but first, a little recap about the hash address in browsers.

Consider, a static website, say, an FAQ page. All the questions are at top, followed by all the answers. You want users who click on a question to automatically be scrolled down to the correct answer. If you know anything about HTML id’s and hash links, you know that it’s usually pretty easy to set-up.

<a href='#ans1'>Q1 ... </a>
<a href='#ans2'>Q2 ... </a>
<a href='#ans3'>Q3 ... </a>
<a href='#ans4'>Q4 ... </a>

<p id="ans1" class='answer'>Answer 1</p>
<p id="ans2" class='answer'>Answer 2</p>
<p id="ans3" class='answer'>Answer 3</p>
<p id="ans4" class='answer'>Answer 4</p>

That’s pretty much it, hash addresses were invented to take you to elements with a particular id. But as it actually changed the URL, it did, in fact, support the back and forward buttons. As a result this was used by javascript-based front-end routers to support the back and forward buttons as well.

Now, the :target selector brings some power to CSS as well. As always, the typical use-case for the selector is the one described above. Perhaps you have a bunch of questions and answers on your FAQ page, and not only do you want to scroll down to the correct answer when the user clicks on a question, you also want to highlight the answer. It’s pretty easy with some CSS —

.answer:target {
  background-color: yellow;
}

And, just like that, whichever answer’s id is currently the hash address in the URL-bar will now have a yellow background and he highlighted as such.

That’s pretty much all the technology we need! But with some more CSS we can take this further and have full featured Router built with CSS

Taking the concept further, if each state is hidden by default with CSS and shown when it’s the target, you can achieve what you’re trying to.

There you have it, a full router, built with just HTML and CSS. It might look a little boring right now, but you can get creative with CSS transitions and animations and make this even better to look at.

So where does Angular fit in all this, you ask.

Well, here’s the problem, Angular doesn’t load without javascript and ends up with pretty bad SEO. The usual solutions to this problem is to re-route requests on the server-side for crawlers to make them go through something like phantomJS.

On the other hand, you are told to use something like ng-cloak to make your web app look good while it’s loading, to save your users from the flash of unstyled content (FOUC). So your app looks something like this:

HTML:

<body>
    <div ng-app='myApp' ng-cloak>
        <!-- Angular directives/code goes here -->
    </div>
</body>

CSS:

[ng-cloak] {
    display: none;
}

This hides your page while Angular is loading. No more FOUC.

But you can take this further. You can add a loading indicator or a complete static app that can be used before Angular takes over (very useful for apps that need to open on slow mobile internet connections), and as a fallback for non-javascript environments. The solution is pretty simple.

HTML:

<body>
    <div ng-app='myApp' ng-cloak>
        <!-- Angular directives/code goes here -->
    </div>
    <div class="static">
        <!-- Static Content for web app goes here -->
    </div>
</body>

CSS:

[ng-cloak], .static {
    display: none;
}

[ng-cloak] ~ .static {
    display: block;
}

That’s it! The div with the class static will show while Angular is loading. As soon as Angular loads it’ll remove the ng-cloak cloak directive and the static content will be hidden.

Now, if you use the CSS static router within the static div, you can make the pre-Angular experience closer to the complete one.

HTML:

<body>
    <div ng-app='myApp' ng-cloak>
        <!-- Angular directives/code goes here -->
    </div>
    <div class="static">

       <header>
            <a href="#home">home</a>
            <a href="#about">About Us</a>
            <a href="#contact">Contact Us</a>
        </header>

        <div id="about" class="state">
            <h1>About Us</h1>
            <p>...</p>
        </div>

        <div id="contact" class="state">
            <h1>Contact Us</h1>
            <p>...</p>
        </div>

        <div id="home" class="state">
            <h1>Home</h1>
            <p>...</p>
        </div>

    </div>
</body>

CSS:

[ng-cloak], .static {
    display: none;
}

[ng-cloak] ~ .static {
    display: block;
}

#home {
    display: block;
}

.state:target {
    display: block;
}

.state:target ~ #home {
    display: none;
}

There’s also an extra added bonus. If you are smart about it, the experience can be almost seamless.

Let’s say you have an Angular route called about. The hash address for the route will be #/about. However, HTML ids can’t start with a / and if you have an element and state in your static content with the id ‘about’ it’ll have a URL of #about. It turns out, that ends up working quite well. As soon as Angular loads, it’s actually smart enough to convert #about to #/about automatically. So if the user navigates to different routes with CSS they will automatically be routed to the same state when Angular loads.

All this said, it’s usually a lot of work to be worth it. It’s not usually the CSS, but the actually loading the static content on the server-side that is usually the hard part. But if SEO is important to you, or if you find your apps taking way too long to load, this is actually a very good way to give your users a much better experience, rather than —

Even if you never actually need to use this technique in an Angular app, I hope this post at least taught you about a lesser known awesome CSS selector.

 
53
Kudos
 
53
Kudos

Now read this

The Imperfect Solution

I saw this article by Jeff Sandberg railing against Tailwind. I’m no fan of Tailwind myself, but the article reeked of elitism and gatekeeping. I don’t think Jeff (or anyone else railing against Tailwind) is doing this on purpose, so I... Continue →