Differences Between Static Generated Sites And Server-Side Rendered Apps

Differences Between Static Generated Sites And Server-Side Rendered Apps

Differences Between Static Generated Sites And Server-Side Rendered Apps

Timi Omoyeni

2020-07-02T12:00:00+00:00 2020-07-02T16:33:39+00:00

JavaScript currently has three types of applications that you can build with: Single Page Applications (SPAs), pre-rendering/static generated sites and server-side rendered applications. SPAs come with many challenges, one of which is Search Engine Optimization (SEO). Possible solutions are to make use of Static Site Generators or Server-Side Rendering (SSR).

In this article, I’m going to explain them alongside listing their pros and cons so you have a balanced view. We’re going to look at what static generated/pre-rendering is as well as frameworks such as Gatsby and VuePress that help in creating statically generated sites. We’re also going to look at what server-side rendered (SSR) applications are as well as frameworks like Nextjs and Nuxtjs that can help you create SSR applications. Finally, we’re going to cover the differences between these two methods and which of them you should use when building your next application.

Note: You can find all the code snippets in this article on GitHub.

What Is A Static Site Generator?

A Static Site Generator (SSG) is a software application that creates HTML pages from templates or components and a given content source. You give it some text files and content, and the generator will give you back a complete website, and this completed website is referred to as a static generated site. What this means is that your site pages are generated at build time and your site content does not change unless you add new contents or components and “rebuild” or you have to rebuild your site if you want it to be updated with new content.

Diagram explaining how static site generation works
How static site generation works (Large preview)

This approach is good for building applications that the content does not change too often — sites that the content does not have to change depending on the user, and sites that do not have a lot of user-generated content. An example of such a site is a blog or a personal website. Let’s look at some advantages of using static generated sites.

PROS

  • Fast website: Since all of your site’s pages and content have been generated at build time, you do not have to worry about API calls to the server for content and this makes your site very fast.
  • Easy to deploy: After your static site has been generated, you would be left with static files, and hence, it can be easily deployed to platforms like Netlify.
  • Security: Static generated site are solely composed of static files, the risk of being vulnerable to cyber attacks is minimal. This is because static generated sites have no database, attackers cannot inject malicious code or exploit your database.
  • You can use version control software (e.g git) to manage and track changes to your content. This can come in handy when you want to roll back changes you made to the content on your site.

CONS

  • Content can become stale if it changes too quickly.
  • To update its content, you have to rebuild the site.
  • Build time would increase depending on the size of the application.

Examples of static site generators are GatsbyJS and VuePress. Let us take a look at how to create static sites using these two generators.

Gatsby

According to their official website,

“Gatsby is a free and open-source framework based on React that helps developers build blazing-fast websites and apps.”

This means developers familiar with React would find it easy to get started with Gatsby.

To use this generator, you first have to install it using NPM:

npm install -g gatsby-cli

This will install Gatsby globally on your machine, you only have to run this command once on your machine. After this installation is complete, you can create your first static site generator using the following command.

gatsby new demo-gatsby

This command will create a new Gatsby project that I have named demo-gatsby. When this is done, you can start up your app server by running the following command:

cd demo-gatsby
gatsby develop

Your Gatsby application should be running on localhost:8000.

Gatsby default landing page
Gatsby default starter page (Large preview)

The folder structure for this app looks like this;

--| gatsby-browser.js --| LICENSE --| README.md
--| gatsby-config.js
--| node_modules/ --| src/
----| components
----| pages
----| images
--| gatsby-node.js --| package.json --| yarn.lock
--| gatsby-ssr.js --| public/
----| icons
----| page-data
----| static

For this tutorial, we’re only going to look at the src/pages folder. This folder contains files that would be generated into routes on your site.

To test this, let us add a new file (newPage.js) to this folder:

import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"
const NewPage = () => ( <Layout> <SEO title="My New Page" /> <h1>Hello Gatsby</h1> <p>This is my first Gatsby Page</p> <button> <Link to='/'>Home</Link> </button> </Layout>
)
export default NewPage

Here, we import React from the react package so when your code is transpiled to pure JavaScript, references to React will appear there. We also import a Link component from gatsby and this is one of React’s route tag that is used in place of the native anchor tag ( <a href='#'>Link</a>). It accepts a to prop that takes a route as a value.

We import a Layout component that was added to your app by default. This component handles the layout of pages nested inside it. We also import the SEO component into this new file. This component accepts a title prop and configures this value as part of your page’s metadata. Finally, we export the function NewPage that returns a JSX containing your new page’s content.

And in your index.js file, add a link to this new page we just created:

import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image"
import SEO from "../components/seo"
const IndexPage = () => ( <Layout> <SEO title="Home" /> <h1>Hi people</h1> <p>Welcome to your new Gatsby site.</p> <p>Now go build something great.</p> <div style={{ maxWidth: `300px`, marginBottom: `1.45rem` }}> <Image /> </div> <Link to="/page-2/">Go to page 2</Link> {/* new link */} <button> <Link to="/newPage/">Go to New Page</Link> </button> </Layout>
)
export default IndexPage

Here, we import the same components that were used in newPage.js file and they perform the same function in this file. We also import an Image component from our components folder. This component is added by default to your Gatsby application and it helps in lazy loading images and serving reduced file size. Finally, we export a function IndexPage that returns JSX containing our new link and some default content.

Now, if we open our browser, we should see our new link at the bottom of the page.

Gatsby default landing page with link to a new page
Gatsby landing page with new link (Large preview)

And if you click on Go To New Page, it should take you to your newly added page.

New page containing some texts
New gatsby page (Large preview)

VuePress

VuePress is a static site generator that is powered by Vue, Vue Router and Webpack. It requires little to no configuration for you to get started with it. While there are a number of tools that are static site generators, VuePress stands out from amongst the pack for a single reason: its primary directive is to make it easier for developers to create and maintain great documentation for their projects.

To use VuePress, you first have to install it:

//globally
yarn global add vuepress # OR npm install -g vuepress //in an existing project
yarn add -D vuepress # OR npm install -D vuepress

Once the installation process is done, you can run the following command in your terminal:

# create the project folder
mkdir demo-vuepress && cd demo-vuepress # create a markdown file
echo '# Hello VuePress' > README.md # start writing
vuepress dev

Here, we create a folder for our VuePress application, add a README.md file with # Hello VuePress as the only content inside this file, and finally, start up our server.

When this is done, our application should be running on localhost:8080 and we should see this in our browser:

A VuePress webpage with a text saying ‘Hello VuePress’
VuePress landing page (Large preview)

VuePress supports VueJS syntax and markup inside this file. Update your README.md file with the following:

# Hello VuePress
_VuePress Rocks_
> **Yes!**
_It supports JavaScript interpolation code_
> **{{new Date()}}**
<p v-for="i of ['v','u', 'e', 'p', 'r', 'e', 's', 's']">{{i}}</p>

If you go back to your browser, your page should look like this:

Updated VuePress page
Updated Vuepress page (Large preview)

To add a new page to your VuePress site, you add a new markdown file to the root directory and name it whatever you want the route to be. In this case, I’ve gone ahead to name it Page-2.md and added the following to the file:

# hello World
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.

And now, if you navigate to /page-2 in your browser, we should see this:

A VuePress webpage containing hello world
A “Hello World” page in VuePress (Large preview)

What Is Server-Side Rendering? (SSR)

Server-Side Rendering (SSR), is the process of displaying web-pages on the server and passing it to the browser/client-side instead of rendering it in the browser. Server-side sends a fully rendered page to the client; the client’s JavaScript bundle takes over and allows the SPA framework to operate.

This means if you have an application that is server-side rendered, your content is fetched on the server side and passed to your browser to display to your user. With client-side rendering it is different, you would have to navigate to that page first before it fetches data from your server meaning your user would have to wait for some seconds before they’re served with the content on that page. Applications that have SSR enabled are called Server-side rendered applications.

A diagram explaining how server-side rendering works
How SSR works (Large preview)

This approach is good for building complex applications that require user interaction, rely on a database, or where the content changes very often. This is because content on these sites changes very often and the users need to see the updated content as soon as they’re updated. It is also good for applications that have tailored content depending on who is viewing it and applications where you need to store user-specific data like email and user preference while also catering for SEO. An example of this is a large e-commerce platform or a social media site. Let us look at some of the advantages of server-side rendering your applications.

Pros

  • Content is up to date because it fetches content on the go;
  • Your site loads fast because it fetches its content on the server-side before rendering it to the user;
  • Since in SSR JavaScript is rendered server-side, your users’ devices have little relevance to the load time of your page and this leads to better performance.

CONS

  • More API calls to the server since they’re made per request;
  • Cannot deploy to a static CDN.

Further examples of frameworks that offer SSR are Next.js and Nuxt.js.

Next.js

Next.js is a React.js framework that helps in building static sites, server-side rendered applications, and so on. Since it was built on React, knowledge of React is required to use this framework.

To create a Next.js app, you need to run the following:

npm init next-app
# or
yarn create next-app

You would be prompted to choose a name your application, I have named my application demo-next. The next option would be to select a template and I’ve selected the Default starter app after which it begins to set up your app. When this is done, we can now start our application

cd demo-next
yarn dev # or npm run dev

Your application should be running on localhost:3000 and you should see this in your browser;

Default Nextjs landing page
Next.js landing page (Large preview)

The page that is being rendered can be found in pages/index.js so if you open this file and modify the JSX inside the Home function, it would reflect in your browser. Replace the JSX with this:

import Head from 'next/head'
export default function Home() { return ( <div className="container"> <Head> <title>Hello Next.js</title> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1 className="title"> Welcome to <a href="https://nextjs.org">Next.js!</a> </h1> <p className='description'>Nextjs Rocks!</p> </main> <style jsx>{` main { padding: 5rem 0; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; } .title a { color: #0070f3; text-decoration: none; } .title a:hover, .title a:focus, .title a:active { text-decoration: underline; } .title { margin: 0; line-height: 1.15; font-size: 4rem; } .title, .description { text-align: center; } .description { line-height: 1.5; font-size: 1.5rem; } `}</style> <style jsx global>{` html, body { padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } * { box-sizing: border-box; } `}</style> </div> )
}

In this file, we make use of Next.js Head component to set our page’s metadata title and favicon for this page. We also export a Home function that returns a JSX containing our page’s content. This JSX contains our Head component together with our main page’s content. It also contains two style tags, one for styling this page and the other for the global styling of the app.

Now, you should see that the content on your app has changed to this:

Nextjs landing page containing ‘welcome to Nextjs’ text
Updated landing page (Large preview)

Now if we want to add a new page to our app, we have to add a new file inside the /pages folder. Routes are automatically created based on the /pages folder structure, this means that if you have a folder structure that looks like this:

--| pages
----| index.js ==> '/'
----| about.js ==> '/about'
----| projects
------| next.js ==> '/projects/next'

So in your pages folder, add a new file and name it hello.js then add the following to it:

import Head from 'next/head'
export default function Hello() { return ( <div> <Head> <title>Hello World</title> <link rel="icon" href="/favicon.ico" /> </Head> <main className='container'> <h1 className='title'> Hello <a href="https://en.wikipedia.org/wiki/Hello_World_(film)">World</a> </h1> <p className='subtitle'>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatem provident soluta, sit explicabo impedit nobis accusantium? Nihil beatae, accusamus modi assumenda, optio omnis aliquid nobis magnam facilis ipsam eum saepe!</p> </main> <style jsx> {` .container { margin: 0 auto; min-height: 100vh; max-width: 800px; text-align: center; } .title { font-family: "Quicksand", "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 22px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } `} </style> </div> )
}

This page is identical to the landing page we already have, we only changed the content and added new styling to the JSX. Now if we visit localhost:3000/hello, we should see our new page:

A Nextjs webpage containing ‘Hello world’
A “Hello World ” page in Next.js (Large preview)

Finally, we need to add a link to this new page on our index.js page, and to do this, we make use of Next’s Link component. To do that, we have to import it first.

# index.js
import Link from 'next/link' #Add this to your JSX
<Link href='/hello'>
<Link href='/hello'> <a>Next</a>
</Link>

This link component is how we add links to pages created in Next in our application.

Now if we go back to our homepage and click on this link, it would take us to our /hello page.

Nuxt.js

According to their official documentation:

“Nuxt is a progressive framework based on Vue.js to create modern web applications. It is based on Vue.js official libraries (vue, vue-router and vuex) and powerful development tools (webpack, Babel and PostCSS). Nuxt’s goal is to make web development powerful and performant with a great developer experience in mind.”

It is based on Vue.js so that means Vue.js developers would find it easy getting started with it and knowledge of Vue.js is required to use this framework.

To create a Nuxt.js app, you need to run the following command in your terminal:

yarn create nuxt-app <project-name>
# or npx
npx create-nuxt-app <project-name>

This would prompt you to select a name along with some other options. I named mine demo-nuxt and selected default options for the other options. When this is done, you can open your app folder and open pages/index.vue. Every file in this folder file is turned into a route and so our landing page is controlled by index.vue file. So if you update it with the following:

<template> <div class="container"> <div> <logo /> <h1 class="title"> Hello Nuxt </h1> <h2 class="subtitle"> Nuxt.js ROcks! </h2> <div class="links"> <a href="https://nuxtjs.org/" target="_blank" class="button--green" > Documentation </a> <a href="https://github.com/nuxt/nuxt.js" target="_blank" class="button--grey" > GitHub </a> </div> </div> </div>
</template>
<script>
import Logo from '~/components/Logo.vue'
export default { components: { Logo }
}
</script>
<style>
.container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center;
}
.title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px;
}
.subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px;
}
.links { padding-top: 15px;
}
</style>

And run your application:

cd demo-nuxt
# start your applicatio
yarn dev # or npm run dev

Your application should be running on localhost:3000 and you should see this:

Default Nuxtjs landing page
Nuxt.js landing page (Large preview)

We can see that this page displays the content we added in to index.vue. The router structure works the same way Next.js router works; it renders every file inside /pages folder into a page. So let us add a new page (hello.vue) to our application.

<template> <div> <h1>Hello World!</h1> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Id ipsa vitae tempora perferendis, voluptate a accusantium itaque vel ex, provident autem quod rem saepe ullam hic explicabo voluptas, libero distinctio?</p> </div>
</template>
<script>
export default {};
</script>
<style>
</style>

So if you open localhost:3000/hello, you should see your new page in your browser.

A Nuxtjs webpage containing ‘Hello World’
“Hello World” page in Nuxtjs (Large preview)

Taking A Closer Look At The Differences

Now that we have looked at both static-site generators and server-side rendering and how to get started with them by using some popular tools, let us look at the differences between them.

Static Sites GeneratorsServer-Side Rendering
Can easily be deployed to a static CDNCannot be deployed to a static CDN
Content and pages are generated at build timeContent and pages are generated per request
Content can become stale quicklyContent is always up to date
Fewer API calls since it only makes it at build timeMakes API calls each time a new page is visited

Conclusion

We can see why it is so easy to think both static generated sites and server-side rendered applications are the same. Now that we know the differences between them are, I would advise that we try to learn more on how to build both static generated sites and server-side rendered applications in order to fully understand the differences between them.

Further Resources

Here are some useful links that are bound to help you get started in no time:

Smashing Editorial (ks, ra, il)

Smashing Podcast Episode 19 With Andy Bell: What Is CUBE CSS?

Smashing Podcast Episode 19 With Andy Bell: What Is CUBE CSS?

Smashing Podcast Episode 19 With Andy Bell: What Is CUBE CSS?

Drew McLellan

2020-06-30T05:00:00+00:00 2020-07-04T17:04:00+00:00

Photo of Andy BellToday, we’re talking about CUBE CSS. What is it, and how does it differ from approaches such as BEM, SMACSS, and OOCSS? I spoke to its creator, Andy Bell, to find out.

Show Notes

Note: Listeners of the Smashing Podcast can save a whopping 40% on Andy’s Learn Eleventy From Scratch course using the code SMASHINGPOD.

Weekly Update

Transcript

Drew McLellan: He is an educator and freelance web designer based in the U.K. and has worked in the designer web industries for well over a decade. In that time he’s worked with some of the largest organizations in the world, like Harley-Davidson, BSkyB, Unilever, Oracle, and the NHS. Alongside Heydon Pickering, he’s the co-author of Every Layout, as well as running Front-End Challenges Club which is focused on teaching front-end development best practice via short, fun challenges.

Drew: His latest venture is Piccalilli, a website newsletter with tutorials and courses to help you level up as a front-end developer and designer. So we know he’s an experienced developer and educator, but did you know he was the first person allowed to compete at Crufts with a panda?

Drew: My Smashing friends, please welcome, Andy Bell. Hi, Andy, how are you?

Andy Bell: I’m smashing, thanks. How are you?

Drew: I’m very good, thank you very much. Now I wanted to talk to you today about something that you posted on your site, Piccalilli, about a CSS methodology that you’ve developed for yourself over recent years. First of all, I guess we should probably explore what we mean by a CSS methodology because that could mean different things to different people. So when you think of the CSS methodology, what is it to you?

Andy: That’s a good, hard question to start with, Drew. Appreciate that, thank you!

Drew: Welcome!

Andy: It’s a tricky one. So, for context, I’ve used BEM for a long time, and that is Block Element Modifier. That’s been around for a long time. The way that I look at a CSS methodology is, it’s not a framework, it’s an organization structure. That’s how I like to see it. It’s like a process almost. It’s like you’ve got a problem that you need to solve with CSS and you use the methodology to solve it for you and keep things predictable and organized. BEM’s just legendary for that because it’s been wildly successful.

Andy: Then you could almost qualify things like the style components and that sort of thing. You can almost say that they’re methodology orientated even though they’re a bit more framework entwined, but still, it’s a methodology of breaking things into tiny molecules. So essentially that’s what I’m trying to do with CUBE CSS as well. A thinking structure, I think I described it as.

Drew: So it’s an application of process for how you architect and you write CSS, and it’s not so much anything that’s based on tools or any other sort of technology, it’s just a sort of work flow. So there’s lots of different approaches out there. You mentioned BEM. There’s SMACSS, OOCSS, Atomic CSS. And then you’ve got these sort of unusual lovechild approaches like ABEM. Have you seen that one?

Andy: Yeah.

Drew: Why publish your own?

Andy: Yeah, yeah. Why make your own? That’s a very good question. I think those who know me well know I like to sail against the tide a lot. It’s mainly because I tend to do lots of varied projects as well, in varied teams. So it’s very hard, I’ve found, to work with BEM with a traditional developer because they’re used to using CSS for what CSS is all about: the cascade, et cetera, and that’s why I sort of stole that from the language.

Andy: On the other flip side is that less structured methodologies, it’s harder to work with a programmer, JS sort of person because they like structure and organization and small components, which is understandable working with the language that they work in.

Andy: So I found myself in these positions where I was working with different types of people, different types of projects where one methodology wasn’t quite working. Over the years, I’ve just been playing around with this idea of how things go, and then there’s the stuff me and Heydon did, Every Layout, which sort of enforced the big part of it, which is the C, the composition part, and then I’ve just sort of evolved it very rapidly over the last six months.

Andy: The only reason I wrote an article about it was because I was just doing this course and I thought I’d better write some supplementary material to go with it so people understand it, and it’s absolutely blown up. So maybe we’re not over methodologies quite yet, Drew.

Drew: So the course material that you’ve been putting together actually uses CUBE CSS as its methodology, does it?

Andy: Yeah. So a good 50% of the course is actually front-end, even though it’s a course unlimited. It’s so, so deeply entwined in the way that we build the front-end that I couldn’t just say, “Oh, by the way, this is how I write a nice CSS,” and then leave it. I know people like to get into the detail, so I was like, what I’ll do is, I’ll write this post that’s really long and really detailed and then if someone wants to skill up and really understand what we’re doing, then they can do, and the rest is from there. And here we are today, Drew, sitting and chatting about it.

Drew: So if somebody already understands BEM and is maybe already using BEM, as an example, because that’s probably one of the widest adopted methodologies, isn’t it? So if they’ve already got an understanding of BEM and they’re coming to CUBE, is that something that they would find easy to adopt? Are there many similarities or is it completely different?

Andy: Yeah. I’d say going from BEM to CUBE is probably a smooth transition, especially the way I like to still write the CSS for CUBE. So the majority of stuff’s happening at a higher level. So it’s happening at the cascade level and it’s happening global CSS, using the utilities to do a lot of the stuff. But when you come into the nuts and bolts of it, it’s very BEM-like components, blocks and elements. The only thing that’s sort of different from BEM is, instead of having modifiers, we use this thing called exceptions instead which is, instead of using CSS classes, it turns to data attributes, which I think gives a nice bit of separation and a real exception, which is what a modifier should be.

Andy: Part of the reason why I’ve sort of sailed away from BEM was because I found the way I was working with it, especially in design systems, was modifiers were dominated and it became a problem because it was like, what is the role of my block at this point? Because if I’m modifying it to the point where it’s unrecognizable regularly, then is this methodology still working how it’s supposed to work?

Andy: Then there’s the whole design token stuff, the stuff that Jina did with the Lightning Design System which we’ve all started adopting now. The utility class stuff really started to make sense with that approach. So I just sort of smushed all the things I like about other people’s work and slotted into my own instead.

Drew: You talk about with BEM, the sort of modifier approach kind of getting out of control. Was that one of the main pain points with BEM that CUBE tries to address?

Andy: Yeah, absolutely. I do like the modifier approach with BEM, it does make sense. What I like about BEM is something that I still do, is the double underscore for an element, and then there’s the double dash for a modifier. I like that way of organizing things. It was just a case of okay, well, a lot of the modifiers I can actually account for with utility classes and then the other bits…

Andy: So the example I use in the article is, imagine you’ve got a card and then the card is flipped, so the content appears before the image. So then that makes sense, to see display: flex and then you reverse it, reverse the order. That makes sense, to have an exception rule for that because it’s an exception of the card’s default state, and that’s how I like to see it. It’s like an affected state on that component, is what an exception is, and that makes sense.

Andy: With a lot of the stuff that I’ve done more recently, the modern front-end stack with reactive JavaScript, there’s a lot of state changing and it’s makes sense to handle it appropriately between CSS and JavaScript because they are becoming more and more entwined with each other, whether you like it or not. It’s a common language for them. As you can see by my face, very much not, but there you go. You’re probably thinking, “Actually, I’ve been working with react quite a lot recently, so I’m the other way round.” So I can see that as well.

Drew: So let’s get into CUBE then. So CUBE stands for Composition Utility Block Exception. Is that right?

Andy: Yeah. That’s the one.

Drew: What on Earth does that mean?

Andy: Oh, mate, you should have heard it before! I was doing a talk last year. I did a talk, and it was called Keeping it Simple with CSS that scales, and in there I sort of introduced an earlier version of it called CBEUT, which was Cascade Block Element Utility Token. It was rubbish. I hated the name of it. I did it a couple of times, this talk last year, and I really didn’t like the name. Then when I came to doing this stuff this year, I thought, “I really need to think about what it actually is and what it’s called.” I think CUBE is a little less rubbish. I think that’s the best way I can describe it.

Andy: But then, names are always rubbish for these things, aren’t they? I mean, like BEM, what a rubbish name that is! But we all do it. Look at Jamstack: that’s a terrible name as well, isn’t it?

Drew: My lips are sealed!

Andy: Your boss is going, “What?” It’s true. It’s just the way it is, isn’t it, in our industry.

Drew: It seems that a lot of the CSS methodologies try and work around some of the features of CSS, things like the cascade. My understanding from what I read is, CUBE tries to make use of those sort of features and properties of CSS.

Andy: Yeah. A good analogy for it is SCSS, like Sass, is an extension of the natural CSS language, isn’t it? You’re pretty much right in CSS still. So CUBE CSS is like that. So it’s an extension of CSS. So we should still write CSS, as CSS should… well, it’s supposed to be written. Let’s be honest, that how it’s supposed to be written, is the name gives it away: Cascading Style Sheets. So it’s embracing that again because what I’ve found is that I’ve gone all the way down to the micro-optimization level. I’ve been down the path that I see a lot of people going down recently where… and I’ve mentioned this in the article as well, where I can see… there’s some evidence of it recently. I’ve spotted people have been creating spacer components and stuff like that, and I understand that problem, I’ve been in that situation.

Andy: The way I fixed it was, instead of drilling down and trying to micro-optimize, I actually started thinking about things on a compositional level instead because it doesn’t matter how small your components are, at some point they’re going to be pages, they’re going to be views. You cannot avoid that, that’s how it’s going to be. So instead of trying to say, “Right, I need these tiny little helpers to do this layout,” you say, “Right, I’ve got a contact page view, or a product page view, and that’s a skeletal layout composition. Then inside of that I can slot whatever components I want in there.” We’ve got things like Grid and Flexbox now which make that much more achievable, and you can essentially put whatever you want inside of the skeletal layout, it doesn’t matter. Then the components should, at that point, behave how you want them to behave, with or without container queries.

Drew: This is the composition part of CUBE where you’re looking at things at more of a macro level, looking at how components can be composed into a view to create the sort of pages that you need to create for a site or an app or what have you.

Andy: So it’s creating rules, essentially. It’s like guidance. It’s trying to get guidelines for something. It’s not like a strict rules, like, you should do this, you should do that. That’s essentially what you’re doing with the browser, with this methodology, is you’re saying… you’re not forcing it to do anything. You’re saying, “Look, ideally, if you could lay it out like this, that would be great, but I understand that that might not be the case so here’s some bounds and some upper and lower levels that we can work with. Do what you can, and cheers.” Then you just chuck some components at it and let it just do what it does. You add enough control in there for it to not look rubbish.

Andy: So a good example would see… we do a layout in Every Layout called the switcher, which essentially will in-line items until a certain point where the calculation that works out how wide it should will just stack them on top of each other. But because we add margin to the in-line and the block, it works, regardless of what the state of it is, it still looks fine. That’s the idea, is that we’re not telling the browser to say, “You must layer this three column grid out.” We’re saying, “If you can layer a three column grid out, do it. Otherwise, just stack and space instead.” So it’s that sort of methodology, of letting the browser do its job really.

Drew: Many of the different approaches that have come along for CSS over the last few years have very much focused on the component level of dealing with everything like it’s a component. CUBE doesn’t downplay that component aspect so much, it just gives this extra concept over the top of taking those components and composing them into bigger layouts, rather than just saying the layout’s just another component.

Andy: Yeah, that’s a good point, yeah. I think the thing to say about components is they’re important, especially in modern front-end stuff. We do a lot of component stuff, system stuff. But the way I see a component is, it’s a collection of rules that extend what’s already been done.

Andy: The point I make in the article is, by the time you get down to the block level, most of your styling has actually been done, and really what your component is doing is dotting the Is and crossing the Ts and it’s saying, “Right, in this context…” So for a card, for example, make the image have a minimum height of X, and add a bit of padding here. Do this, that and the other. Put a button here. It’s just sort of additional rules on top of what’s already been inherited from the rest of the CSS. I think that’s probably the best way to describe it.

Andy: Whereas in BEM, the component is the source of truth. Until you put that class on the element, nothing has been applied at that point, and that method works. I just found I wrote more CSS by doing that, and more repetitive CSS, which I don’t like doing.

Drew: Would you consider the typography and the colors and the vertical rhythms, spacing, and all of that, is all that part of the idea of composition in this model?

Andy: Yeah. In a global CSS, yeah, absolutely. The vertical rhythm especially, and the flow. We did an article on that at 24 ways, didn’t we, a couple of years ago, the flow and rhythm component. That was a sort of abstract from this approach as well, where you set a base component which essentially uses the lobotomized owl selector. I don’t know how I’m going to describe that on the radio, but we will. We’ll just put, I think, in the show notes about the Heydon article or something. But essentially that, it selects child elements… sorry, sibling elements.

Andy: So it says, “Right, every element that follows element X have margin top of CSS costs and property value,” and then the beauty of that is then you can set that CSS custom property value on a compositional context as well. So you can say, “Right, in this component, if there’s some flow on the go, we’ll set flow space to actually be two rem because we want it to be nice and beefy, the wide space.” Then in another one you might say, “Actually, I want flow space to be half a rem because I want it to be tight.” This is all happening, all the control is coming from a higher level and then what you’re doing is, you’re adding contextual overrides rather than reinventing it each time, reinventing the same thing over and over again.

Drew: So that’s the C, Composition. Next we’ve got U, which is Utility. So what do we mean by utility?

Andy: So it’s a class that does one job, and it does it really well. That could be an implementation of a design token which… it’s an abstract of properties. Usually it’s colors or typography styles, sizing, and rules like that. The idea is you generate utility classes that apply those. So you’ve got a utility that will apply background primary, which is like the primary color, and then color primary, which is the text color. Then you might generate some spacing tokens for marginal padding, and all those sorts of things. They just do one job. They just add that one spacing rule, that one color rule, and that’s it.

Andy: But then you’ve got other utilities as well. So a good example is a wrapper utility. What that will do is, it will put a maximum width on an element and then it will put left and right auto margin to sit it in the middle of the thing. So it’s just got one job, and it’s just doing it efficiently and well.

Andy: So you’ve got your global styles, you’ve done a lot of your typography settings and a lot of your flooring space. Your composition’s then giving context and layout. Then utilities are applying tokens directly to elements to give them those styling that you need. So like a heading, for example, you’re saying, “I want this to be this size and I want it to have this lead in, and I want it to have this measure.” Then at that point… this is what I was saying about the blocks… then you go further down the stack, and you’ve already done most of the work at the point.

Andy: So they give you this really efficient way of working, and because HTML sort of streams down the pipe as well, it’s really nice to abstract the workload onto HTML rather than CSS as well, I’ve found. I used to really get into utility classes, like in this sort of old curmudgeon style of, “Oh, separation of concerns,” but I actually think it’s a really decent way of working. I mention in the article that I actually like Tailwind CSS. I think it does work, and I really like using it for product typing because I can really put something out really quick. But I think it just goes a little bit too far, does Tailwind, whereas I like to rain it in when it goes beyond just applying a single rule on a class. So that’s it, I think. Do you?

Drew: So, yeah, you talk in the article a lot about design tokens, which is something that we’ve talked about on the Smashing Podcast with Jina Anne back in episode three, I think it was. So it sounds like design tokens are a really fundamental aspect.

Andy: Yeah. Oh, God, yeah. I remember so vividly when Jina was doing the Lightning Design System stuff because I was building a design system at the time, or something that resembled a design system, and we were struggling to get executive approval of it. When the Lightning Design System came out, I literally just sent them link after link and I said, “This is what we’re doing. We’re building a design system. This is what Salesforce are currently using it for.” I remember her work at the time actually helped me to get stuff through the door.

Andy: Then the design token stuff has always stuck with me as being a really good way of applying these rules. Because I’m a designer by trade, so I can just sort of see that organization and the ability to organize and create a single source of truth being really useful because it’s something we’ve not really had in digital design, especially in the Adobe era of working with Photoshop and stuff, we just didn’t have that luxury. We had it in print with the Pantone Book but we didn’t have it in digital with random hex codes all over the shop.

Andy: So it’s just great. I love that level of control. Actually, I think it aids in creativity because you’re not thinking about unimportant stuff anymore, you’re just thinking about what you’re doing with it.

Drew: Does the implementation of those design tokens matter particularly with the approach? Is it always CSS custom properties?

Andy: I think that’s a really important point with CUBE. Some of the responses I’ve had, people have struggled with this a little bit. There’s no mention of technology in it whatsoever. The only technology that’s consistent is CSS. You can do it however you want. You could do all this with whatever CSS and JS things people are doing now, or you could it with just Vanilla CSS. You could do it with Sass. I do it with Sass. Less, if that’s what you’re still doing. All these available technologies, post CSS, all these things. You can do however you want to do it, it doesn’t matter.

Andy: The idea is that if you follow those sort of constructs, you’ll be fine. That’s the idea behind it. It’s a very loose and not strict as some of the methodologies are. I’ve seen it with BEM especially, people get really ingrained in the principles of BEM to the point where it’s like you’re not even solving the problem anymore. I think that you’ve got to be flexible. I said it in this talk last year. I was like, “If you stick to your guns too tightly, you can actually cause problems for yourself in the long run because you try and follow a certain path, and you know it’s not working anymore.” You should always be flexible and work with a system rather than working to the letter to it.

Drew: So the B, the B is Block. You’ve talked about this idea, that by the time you get down to the block level, most of everything should be in place, and then the block level styling is only really concerned with the actual very detail of a particular component. Generally, is the concept of a block similar to what people will be familiar with?

Andy: Oh, absolutely, yeah. So imagine your BEM component and take all the visual stuff out of it, and that’s what you’re left with, essentially, the block. This is what I struggled to articulate when I first started thinking of this methodology. A block is actually really a C, it’s a composition, but that makes it really difficult because you’re into recursive territory there and I think people’s brains would explode. But really that’s what a block is, it’s actually another compositional layer but more of a sort of strict context, so like your card, your button, your carousel, if that’s what you like doing still, and all that sort of stuff.

Andy: It’s like a specific thing, a component, and then inside of there you’re setting specific rules for it to follow, really ignoring the rest so you’re not… You might apply tokens in the blocks, and I do do that still, but really it’s more layout orientated, is a block, as far as I work with them, or at least taking the token and applying it in a specific way, like a button hover status, stuff like that. So really your block should be tiny by the time you get down to them, they shouldn’t be really doing much at all.

Drew: So it could be as small as a hyperlink.

Andy: Yeah.

Drew: But it could also be a compound collection of other blocks?

Andy: Yeah. Like a module sort of thing. You could definitely do that. Because, again, that goes back to the sort of compositional aspect of it, is that whatever goes in it shouldn’t matter. The good example of that is like the card. So the content of a card is, say, like a heading, a summary and a button. You shouldn’t really be specifically targeting those three elements. You should be saying, “Look, anything that happens to find itself in content, have some flow rules in there and have some sort of compositional layout rules,” and then it doesn’t matter what you put in there. You might decide that you want to put an image in that content thing and it should just work, it should just look fine.

Andy: That’s the whole point of working with CSS. It’s a very forgiving way of working with CSS. You’re making your life a lot easier by being less rigid because when stuff accidentally finds itself in something, which it will, it doesn’t look horrific as it could do if you were being more specific about things, is what I’ve found.

Drew: I definitely need a lot of forgiveness around my CSS!

Andy: I know you do!

Drew: Cheers! So that’s the B. The last thing is E: E is Exception. Now we’re not talking about error messages, are we?

Andy: No, no. It’s a sort of-

Drew: We’re not talking about JavaScript exceptions.

Andy: Yeah, yeah. There should be none of that at this point. I should hope not anyway, otherwise you really are in the woods at that point: I don’t think I’m going to be able to help you! The whole idea of this is… so you’ve created the context with your block, and an exception is exactly that, it’s like an exception to the rule: so a flipped card, or it might be a ghost button. So you know those buttons that have just got a border and a transparent background? That would be an exception because a button has probably got a solid background color and then the label color. So it’s creating a sort of distinct state of variation.

Andy: The reason why I do this with data attributes instead of classes, and the reason why that is is because a) I think it’s nice to have a distinction. So when you’re scanning through lots of HTML, you can see data, hyphen something, you’re like, “Right, okay, something has definitely changed on this element.” The other thing is that it’s very nice to give JavaScript access to that state, and vice versa as well. So I really like applying state with data attributes in JavaScript. I think that is essentially what they’re for, a sort of communication layer. The harmony between them seems to work really well.

Andy: So a good example is, say you’ve got a status message and then JavaScript will add data state is either success, error or information, or something. You can then hook into that with your exception styles in CSS. So you know that’s an exception of the status component and it’s going against its default state. So it’s just a really handy way of working with things. It’s predictable on both ends: it’s predictable on the CSS end, and it’s predictable on the JavaScript end as well.

Drew: I guess it’s quite nice that something that class names don’t give you is a property and value. So if you want to have something like that which is the state, and it can either be success or failure or warning or what have you, you can specifically address that state property and flip its value. Whereas with a big long list of class names, if you’re manipulating that in JavaScript, for example, you’re going to have to look at each one of them and add that business logic in there that says, “I can only set one of these,” and what happens if two of those classes are applied to the same element? You can’t get that with a data attribute, it only has one value.

Andy: Yeah. That’s a good way of saying that, yeah. It is very helpful, I’ve found, to work like that.

Drew: That’s quite interesting. I don’t think I’ve seen any other methodologies that take that approach. Is that completely unique to CUBE, doing that?

Andy: It might be. I don’t really pay much attention to other stuff, which I should do. Someone else is probably doing that. I’ll tell you now, it’s been the most controversial aspect of it. Some people really did not like the idea of using data attributes. The thing is as well, and how I respond, is, do what you want. We’re not telling you to do things in a certain way, it’s just suggestions. If you want to do exceptions to CSS classes, like modifiers, then knock yourself out. The CUBE police aren’t going to come knocking at your door. It’s absolutely fine.

Andy: The CUBE thing is a thinking thing, it’s a structure. You apply that structure however you want to apply it, with what tooling you want, or whatever technology you want. As long as you keep things consistent, that’s the important thing.

Drew: So there’s no such thing as pure CUBE?

Andy: The way I write it is pure CUBE, Drew. Everyone else is just a fake, it’s just a weak immitation.

Drew: Apart from to you, no-one can say, “That isn’t textbook CUBE.”

Andy: No, that’s it. No-one can dispute that really, can they? So, yeah, I’ll go with that. Gives you a bit of clout or something, I think, something like that.

Drew: Can you mix and match a CUBE approach with other methodologies? Can you use bits of BEM?

Andy: Yeah, I reckon so. I’ve been thinking about it a little bit because I’m going to do some more stuff on it soon because it’s become quite popular, so people will want more work. One thing I’m going to look at is how to approach using the CUBE methodology with something existing.

Andy: So there’s two opposite ends of the scale. A good question that people have asked is: “How does this work with every layout, the other stuff?” I’m like, basically, every layout is the C. That’s what every layout is, it’s the compositional layer. Then someone else asked, “Well, how would this work with something like Atomic Web Design, like their stuff that Brad Frost did? It’s like, well, you could break those pieces up and apply them at each level. Atomic Design goes all the way down into the micro detail. It’s abstracting that into using, right, okay, well I can apply this with utilities, so the molecules, I think. I can apply that with the utilities, and it’s translating what you know already into this slightly different structure of working.

Andy: Really, it’s a renaming for a lot of things. I’ve not invented anything here, I’ve just sort of, like I say, I’ve just stolen things that I like. I love the way that some of the Atomic Design stuff is thought about. That’s really some smart work. And BEM. The stuff Harry did, the Inverted Triangle CSS, I thought that was really cool. So I’ve just sort of nicked bits that I like from each one of them and sort of stitched them all together into this other hybrid thing, approach. More to come, I think.

Drew: Can the CUBE approach be applied to existing projects that already have CSS in place or is it something you really need to start on a fresh project with?

Andy: That very much depends. So if you’ve got like a bootstrap job and it’s just got thousands of lines of custom CSS, that I’ve definitely been involved in before, then I think you might be trying to put a fire out with a bottle of water at that point. But if you… say, for instance, if you’ve got a rough BEM setup and it’s gone a bit layer-y, you could use CUBE to refactor and actually pull it back into shape again.

Andy: It depends, the answer to that one. But it’s doable, as with everything. If you really want it to work, Drew, you can do it if you want, can’t you? The world is our oyster!

Drew: Especially if your BEM site’s gone layer-y.

Andy: Yeah. Nothing worse than a layer-y BEM site!

Drew: I’ve noticed in the examples that you’ve given… and I’ve got an eagle eye, I’ve seen you’ve been doing this for a while… a lot of your class values in the HTML attribute are wrapped in square brackets.

Andy: Oh, God, yeah. Tell you what, Drew-

Drew: What is that about? What is that about?

Andy: I’ll tell you what, if there’s ever one thing that I’ve done in my whole career that’s just been absolutely outrageously controversial… and you follow me on Twitter, you’ve seen what comes out of my mouth… it’s those bloody brackets! My, God! People either love them or hate them. They’re Marmite, they are.

Andy: The reason I do them is a grouping mechanism. So if you look at the way that they’re structured, the way I do it is, block at the start and then I’ll do a utilities after that. Then what I might do is, in between a block group and a utility group, there might be another block class. So a good example of that would be… we’ll go back to the card again. But then say that there’s a specific block called a CTA, like a call to action. You might have that applied to the card as well, and then your utilities are enforcing the design attributes, so the colors and all that business. So then you’ve got three groups of stuff.

Andy: When you come to it, if you’ve got that order in your head each time, you know, okay, right, this first group’s blocks. Oh, that’s looks like another block. I’ve got that one. Then it’s like, right, they’re definitely utility classes. Then what I might even do is, if there’s a lot of design token implementation, have that in a separate group. So it’s just very clear what each group is doing, and there’s a separation inside of the classes there as well. I’ve found it really helpful. Some people find it incredibly offensive. It’s definitely a do it if you want to do it. Definitely you don’t have to do it.

Andy: It’s quite funny, when I published that article, so many people finished halfway through to ask me, “What is it with these brackets?” I was like, “Did you finish the article? Because there’s a big section at the end where it explains exactly what they’re doing,” to the point where I actually had to write a bit in the middle of the article of, “If the brackets are essentially doing your head in, click here and I’ll skip you all the way down to that explanation bit so you can just read about them.” It can be quite controversial.

Andy: When I’ve worked on really, really complex front-ends… and we did a little bit of stuff together, didn’t we, last year?

Drew: Yeah.

Andy: You’ve seen the sort of design implementation on that project that we were on. It requires that sort of grouping because there’s just so much going on at the time, there’s so much different stuff happening. I’ve just found it really, really useful over the years, and also get lots of questions about it, to the point where I was almost going to write just one page on my website that I could just fire people to to answer the question for them.

Drew: Slash, what’s with the brackets?

Andy: Yeah. Slash, brackets. Have you seen that new Hey Email thing that’s just come out? They’ve bought a domain of itsnotatypo.com, just to answer the whole Imbox, like im with an M rather than an in. Basically, I was like, “I think I need to do that,” like, whatswiththebrackets.com, and just do a one-pager about it.

Drew: It strikes me that the approach with brackets actually could be something that might be useful when using things like Tailwind or something that has a lot of classes because that can-

Andy: Yeah. Oh, God, yes.

Drew: You have classes that are addressing your break points and what have you, and then you’ll have things that are for layout, things that are for color or type, or what have you. So it might also be a useful way of dealing in situations like that.

Andy: I’d definitely agree with that. A good analogy… not analogy. A good bit of info about Tailwind is that I actually quite like Tailwind. I’ve used that on very big projects. The one thing that really opened my eyes to Tailwind though was when I saw a junior developer try to work out what was going on, and it was really, really eye-opening because they just didn’t have a clue what was happening.

Andy: I think that’s one problem I’ve found with these sort of over-engineered approaches, which I think it’s fair to say Tailwind is, is that there’s a certain skill level that is required to work with it. I know the industry tends to have an obsession with seniority now, but there’s still people that are just getting into the game that we need to accommodate, and I think having stuff that’s closer to the language itself is more helpful in those situations because they’re probably learning material that is the language as it is. So I think it’s just a bit more helpful. Especially having a diverse team of people as well. Just food for thought for everyone.

Drew: People might look at all the different methodologies that are out there and say, “This is evidence that CSS is terrible and broken, that we need… all these problems have to be solved by hacking stuff on top. We need tools to fix bits of CSS. We need strict procedures for how we implement it, just to get it to work.” Should the platform be adapting itself? Do we need new bits of CSS to try and solve these problems or are we all right just hacking around and making up funny acronyms?

Andy: I think the power of CSS, I think, is its flexibility. So if you’re going to program CSS, a lot of the knowledge is less of the syntax and more of the workings of a browser and how it works. I think that might be a suggestion, that the problem is that people might not have learnt CSS quite as thoroughly as they thought they might have learnt it, who created these problems. I’ve seen that in evidence myself. I spotted a spacing mechanism that had been invested, which was very complicated, and I thought, “This person has not learnt what padding is because padding would actually fix this problem for them, understanding how padding works and the box model.” That’s not to be snidey about it.

Andy: We work in an industry now that moves at an even faster pace than it has done previously and I think there’s a lot less time for people to learn things in detail. But, on that front, I think CSS still does have work to do in terms of the working group, who I think do a bloody good job. A great, shining example of their work was the Grid spec which was just phenomenal. The way that rolled out in pretty much every browser on day one, I thought that was so good.

Andy: But we’ve got more work to do, I think, and I think maybe the pace might need to increase a little, especially with stuff like container queries, we all love talking about them. Stuff like that I think would help to put CSS in a different light with people, I think. But I think CSS is brilliant, I love it. I’ve never had a problem with it in lots of years really. I do find some of the solutions a bit odd, but there you go.

Drew: What’s the response been like to CUBE since you published the article?

Andy: Mind-blowing. I honestly published it as just supporting material, and that’s all I expected it to be, and it’s just blown up all over the place. A lot of people have been reading it, asking about it, been really interested about it. There’s definitely more to come on it.

Andy: I did say in the article, I said, “Look, if people are actually quite interested in this, I’ll expand on this post and actually make some documentation.” I’ve got bits of documentation dotted around all over the place, but to sort of centralize that, and then I was thinking of doing some workshops and stuff. So there’s stuff to go. It’s how Every Layout started as well. We both had these scattered ideas about layout and then we sort of merged them together. So something like that, I suppose, will come in the future.

Drew: Are there any downsides that you’re aware of to using CUBE? Are there problems that it doesn’t attempt to solve?

Andy: Yeah. This accent, Drew, it just won’t go way, no matter what I do! In all seriousness, I think CUBE’s got as close as I can get to being happy with the front-end, which is saying a lot, I think. You never know, things might change again. This has evolved over more recent years. Give it another five years, I’ll probably be struggling with this and trying something else. I think that’s the key point, is to just keep working on yourself and working on what you know and how you approach things.

Andy: This definitely won’t work for everyone as well, I know that for a fact. I know that from my comments. I don’t expect it to work for everyone. I don’t expect anything to work for everyone. It’s the same with JavaScript stuff: some people like the reactive stuff and some people don’t. It is what it is. We’re all people at the end of the day, we all have different tastes. It’s all about communicating with your teammates at the end of the day, that’s the important thing.

Drew: I know you as a very talented designer and developer and you, like many of us, you’re just working on real projects all day, every day. But you’ve recently started publishing on this site, Piccalilli, which is where the CUBE CSS introduction article was. So Piccalilli is kind of a new venture for you, isn’t it? What’s it all about?

Andy: Very kind of you to say, Drew. You’ve actually worked with me, so that’s high praise. But the Piccalilli thing is an evolution. So I’m a freelancer. I do client work, but I think this has become apparent with the pandemic, that that is not the most sustainable thing in the world in some industries. I think freelancing can be very, very tough, as a developer and designer. It’s something that I’ve been doing it for so long now, 10 years… well, 12 years now actually.

Andy: I fancied doing something a bit different and applying the knowledge that I’ve got and actually sharing it with people. I’ve always been very open and sharing, and I wanted to formalize that. So I created Piccalilli to write tutorials, but mainly for courses that I’m producing: that’s the main meat and potatoes. And then there’s the newsletter which is… people are really enjoying the newsletter because I share cool things I’ve found on the internet every week. That’s the backbone of it. It’s just going really well. That’s essentially where I want to see myself doing more and more full-time, as the years go on, I think.

Drew: So what’s coming next for Piccalilli? Have you got anything that you’ve got coming out?

Andy: Thanks for the door open there, Drew! By the time this recording goes out, the first course will be live: Learn Eleventy From Scratch, and that’s where we learn how to build a Gatsby website! No, you learn how to build an Eleventy site. So you start off with a completely empty directory, there’s nothing in it, it’s empty, and then at the end of it you’ll finish up with this really nice-looking agency site. We learn all sorts in it. You learn how to really go to town with Eleventy. We pull remote data in from places. We use CUBE CSS to build a really nice front-end for it.

Andy: If you want to get into the Jamstack and you want to get into static site generators, or just how to build a nice website, it’s just a really handy course, I hope, for that. It’s currently being edited within an inch of its life as we’re talking. It’s going to be cool, I hope, and useful. But that’s an accumulation of a lot of stuff I’ve been doing over the last couple of years. So it should be fun.

Andy: So buy it, and I’ll do a discount code, do like smashingpod for 40% off, and you can get it when it comes out.

Drew: Amazing. We’ll link that up. Have you figured out how to spell Piccalilli reliably yet?

Andy: I was on with Chris and Dave with the ShopTalk Show and I said on there, “If there’s ever one thing you want to hire me for it’s to write Piccalilli by hand first time without even thinking about it,” because I’ve written that word so many times that I just know exactly how to spell it off by heart. So the answer to your question is yes.

Drew: Well, I’m still struggling, I’ll tell you that much!

Andy: It is hard. Oh, God. I totally empathize. It took me a long time to learn how to spell it but it’s one of those words in our vocabulary. This year I’m trying to spell necessary without making a spelling mistake!

Drew: So I’ve been learning all about CUBE CSS. What have you been learning about lately, Andy?

Andy: Do you know what? This is going to surprise you, Drew. MySQL is what I’ve been learning about recently. So, basically, Piccalilli is totally self-published. It’s an Eleventy site but it’s got an API behind it, and that’s got a MySQL database behind it. The stuff that gives people content that they’ve purchased requires some pretty hefty querying. So I’ve just actually properly invested in some MySQL… especially the difference between joins, which I didn’t actually realize there was a difference between each type of join. So that’s been really useful and it’s resulted in some pretty speedy interactions with the database.

Andy: I used to run this thing called Front-End Challenges Club and when I first launched it it just collapsed and died on itself because MySQL was shoddy to say the least. So I’ve really been learning how to do that because I’m not a backend person at all, I’m a pixel-pusher. So it’s definitely not in my remit. That’s more your neck of the woods, isn’t it? I find it really cool, MySQL. I actually really like writing it. It’s a really nice, instructional language, isn’t it?

Drew: It is, it’s great. I learnt SQL at school.

Andy: Wow!

Drew: We’re talking like 20 years ago now.

Andy: Did they have computers in those days?

Drew: They did, yeah. We had to wind-

Andy: Did you have to write it by hand?

Drew: We had to wind them up! We did. But, I tell you, for a developer, it’s akin to learning your times tables: one of those things that seems like a bit of a chore but once you’re fluent, it just becomes useful time and time again.

Andy: Yeah. For sure. There’s really tangible differences as well. You really see the difference in speed. I really like working with Node because that’s really fast but Node and MySQL is just… not a very common choice, but I think it’s a pretty good choice. I think it works really, really well. So I’m happy with that. As you know, I don’t like writing PHP. So that’s never going to be an option.

Drew: If you, dear listener, would like to hear more from Andy, you can follow him on Twitter where he’s at hankchizljaw. You can find Piccalilli at piccalil.li, where you’ll also find the article describing CUBE CSS, and we’ll also add links to all of those in the show notes, of course.

Drew: Thanks for joining us today, Andy. Did you have any parting words?

Andy: Stay safe, and wear your mask.

Smashing Editorial (il)

Firebase Push Notifications In React

Firebase Push Notifications In React

Firebase Push Notifications In React

Chidi Orji

2020-06-29T13:30:00+00:00 2020-07-05T17:34:08+00:00

Notifications have become a stable part of the web nowadays. It’s not uncommon to come across sites asking for permission to send notifications to your browser. Most modern web browsers implement the push API and are able to handle push notifications. A quick check on caniuse shows that the API enjoys wide support among modern chrome-based browsers and Firefox browser.

There are various services for implementing notifications on the web. Notable ones are Pusher and Firebase. In this article, we’ll implement push notifications with the Firebase Cloud Messaging (FCM) service, which is “a cross-platform messaging solution that lets you reliably send messages at no cost”.

I assume that the reader has some familiarity with writing a back-end application in Express.js and/or some familiarity with React. If you’re comfortable with either of these technologies, then, you could work with either the frontend or backend. We will implement the backend first, then move on to the frontend. In that way, you can use whichever section appeals more to you.

So let’s get started.

Types Of Firebase Messages

The Firebase documentation specifies that an FCM implementation requires two components.

  1. A trusted environment such as Cloud Functions for Firebase or an app server on which to build, target, and send messages.
  2. An iOS, Android, or web (JavaScript) client app that receives messages via the corresponding platform-specific transport service.

We will take care of item 1 in our express back-end app, and item 2 in our react front-end app.

The docs also state that FCM lets us send two types of messages.

  1. Notification messages (sometimes thought of as “display messages”) are handled by the FCM SDK automatically.
  2. Data messages are handled by the client app.

Notification messages are automatically handled by the browser on the web. They can also take an optional data payload, which must be handled by the client app. In this tutorial, we’ll be sending and receiving data messages, which must be handled by the client app. This affords us more freedom in deciding how to handle the received message.

Setting Up A Firebase Project

The very first thing we need to do is to set up a Firebase project. FCM is a service and as such, we’ll be needing some API keys. This step requires that you have a Google account. Create one if you don’t already have one. You can click here to get started.

After setting up your Google account, head on to the Firebase console.

Click on add project. Enter a name for your project and click on continue. On the next screen, you may choose to turn off analytics. You can always turn it on later from the Analytics menu of your project page. Click continue and wait a few minutes for the project to be created. It’s usually under a minute. Then click on continue to open your project page.

Once we’ve successfully set up a project, the next step is to get the necessary keys to work with our project. When working with Firebase, we need to complete a configuration step for the frontend and backend separately. Let’s see how we can obtain the credentials needed to work with both.

Frontend

On the project page, click on the icon to add Firebase to your web app.

Add Firebase to a web project
Add Firebase to a web project. (Large preview)

Give your app a nickname. No need to set up Firebase hosting. Click on Register app and give it a few seconds to complete the setup. On the next screen, copy out the app credentials and store them somewhere. You could just leave this window open and come back to it later.

Firebase web app credentials
Firebase web app credentials. (Large preview)

We’ll be needing the configuration object later. Click on continue to console to return to your console.

Backend

We need a service account credential to connect with our Firebase project from the backend. On your project page, click on the gear icon next to Project Overview to create a service account for use with our Express backend. Refer to the below screenshot. Follow steps 1 to 4 to download a JSON file with your account credentials. Be sure to keep your service account file in a safe place.

Steps for creating a service account credential
Steps for creating a service account credential. (Large preview)

I’ll advise you not to download it until you’re ready to use it. Just remember to come back to these sections if you need a refresher.

So now we’ve successfully set up a Firebase project and added a web app to it. We’ve also seen how to get the credentials we need to work with both the frontend and backend. Let’s now work on sending push notifications from our express backend.

Getting Started

To make it easier to work through this tutorial, I’ve set up a project on Github with both a server and a client. Usually, you’ll have a separate repo for your backend and frontend respectively. But I’ve put them together here to make it easier to work through this tutorial.

Create a fork of the repo, clone it to your computer, and let’s get our front-end and back-end servers started.

  1. Fork the repo and check out the 01-get-started branch.
  2. Open the project in your code editor of choice and observe the contents.
  3. In the project root, we have two folders, client/ and server/. There’s also a .editorconfig file, a .gitignore, and a README.md.
  4. The client folder contains a React app. This is where we will listen for notifications.
  5. The server folder contains an express app. This is where we’ll send notifications from. The app is from the project we built in my other article How To Set Up An Express API Backend Project With PostgreSQL.
  6. Open a terminal and navigate to the client/ folder. Run the yarn install command to install the project dependencies. Then run yarn start to start the project. Visit http://localhost:3000 to see the live app.
  7. Create a .env file inside the server/ folder and add the CONNECTION_STRING environment variable. This variable is a database connection URL pointing to a PostgreSQL database. If you need help with this, check out the Connecting The PostgreSQL Database And Writing A Model section of my linked article. You should also provide the PORT environment variable since React already runs on port 3000. I set PORT=3001 in my .env file.
  8. Open a separate terminal and navigate to the server/ folder. Run the yarn install command to install the project dependencies. Run yarn runQuery to create the project database. Run yarn startdev to start the project. Visit http://localhost:3001/v1/messages and you should see some messages in a JSON format.
Frontend and backend servers running
Frontend and backend servers running. (Large preview)
React frontend app running
React frontend app running. (Large preview)
Express backend app running
Express backend app running. (Large preview)

Now that we have our front-end and back-end apps running, let’s implement notifications in the backend.

Setting Up Firebase Admin Messaging On The Backend

Sending out push notifications with FCM on the backend requires either the Firebase admin SDK or the FCM server protocols. We’ll be making use of the admin SDK in this tutorial. There’s also the notifications composer, which is good for “testing and sending marketing and engagement messages with powerful built-in targeting and analytics”.

In your terminal, navigate to the server/ folder and install the Admin SDK.

# install firebase admin SDK
yarn add firebase-admin

Open your .env file and add the following environment variable.

GOOGLE_APPLICATION_CREDENTIALS="path-to-your-service-account-json-file"

The value of this variable is the path to your downloaded service account credentials. At this point, you probably want to go back to the section where we created the service account for our project. You should copy the admin initialization code from there and also download your service account key file. Place this file in your server/ folder and add it to your .gitignore.

Remember, in an actual project, you should store this file in a very secure location on your server. Don’t let it get into the wrong hands.

Open server/src/settings.js and export the application credentials file path.

# export the service account key file path
export const googleApplicationCredentials = process.env.GOOGLE_APPLICATION_CREDENTIALS;

Create a file server/src/firebaseInit.js and add the below code.

import admin from 'firebase-admin'; import { googleApplicationCredentials } from './settings' const serviceAccount = require(googleApplicationCredentials); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: 'your-database-url-here'
}); export const messaging = admin.messaging();

We import the admin module from firebase-admin. We then initialize the admin app with our service account file. Finally, we create and export the messaging feature.

Note that I could have passed the path to my service account key file directly, but it is the less secure option. Always use environment variables when dealing with sensitive information.

To check that you completed the initialization successfully, open up server/src/app.js and include the following lines.

import { messaging } from './firebaseInit'
console.log(messaging)

We import the messaging instance and log it in the console. You should see something like the picture below. You should remove these once you verify that your admin is set up correctly.

Console log of messaging feature
Console log of messaging feature. (Large preview)

If you run into any problems, you can check out the 02-connect-firebase-admin branch of my repo for comparison.

Now that we’ve successfully setup admin messaging, let’s now write the code to send the notifications.

Sending Push Notifications From The Backend

FCM data message configuration is very simple. All you have to do is supply one or more target(s) and a JSON of the message you wish to send to the client(s). There are no required keys in the JSON. You alone decide what key-value pairs you want to include in the data. The data messages form works across all platforms, so our notification could also be processed by mobile devices.

There are additional configurations for other platforms. For example, there’s an android settings that only work with android devices and apns settings that work on only iOS devices. You can find the configuration guide here.

Create a file server/src/notify.js and enter the below code.

import { messaging } from './firebaseInit'; export const sendNotificationToClient = (tokens, data) => { // Send a message to the devices corresponding to the provided // registration tokens. messaging .sendMulticast({ tokens, data }) .then(response => { // Response is an object of the form { responses: [] } const successes = response.responses.filter(r => r.success === true) .length; const failures = response.responses.filter(r => r.success === false) .length; console.log( 'Notifications sent:', `${successes} successful, ${failures} failed` ); }) .catch(error => { console.log('Error sending message:', error); });
};

We created a function that accepts an array of token strings and a data object. Each token string represents a device that has accepted to receive notifications from our back-end application. The notification will be sent to each client in the tokens array. We’ll see how to generate the token in the front-end section of the tutorial.

The messaging instance’s sendMulticast method returns a promise. On success, we get an array from which we count the number of successes as well as failed notifications. You could certainly handle this response anyhow you want.

Let’s use this function to send out a notification each time a new message is added to the database.

Open server/src/controllers/message.js and update the addMessage function.

import { sendNotificationToClient } from '../notify'; export const addMessage = async (req, res) => { const { name, message } = req.body; const columns = 'name, message'; const values = `'${name}', '${message}'`; try { const data = await messagesModel.insertWithReturn(columns, values); const tokens = []; const notificationData = { title: 'New message', body: message, }; sendNotificationToClient(tokens, notificationData); res.status(200).json({ messages: data.rows }); } catch (err) { res.status(200).json({ messages: err.stack }); }
};

This function handles a post request to the /messages endpoint. Once a message is successfully created, a notification is sent out by the sendNotificationToClient function followed by the response to the client. The only missing piece in this code is the tokens to send the notifications to.

When we connect the client app, we’ll copy the generated token and paste it in this file. In a production app, you’ll store the tokens somewhere in your database.

With this last piece of code, we’ve completed the back-end implementation. Let’s now switch over to the frontend.

The corresponding branch in my repo at this point is 03-send-notification.

Setting Up Firebase Messaging Notifications On The Client

Let’s take a look at the main components of our front-end React app.

Open up client/src/App.js and inspect the content. I’ll leave out most of the import statements and just look at the program logic.

# library imports import { Messaging } from './Messaging'; axios.defaults.baseURL = 'http://localhost:3001/v1'; const App = () => { return ( <Fragment> <ToastContainer autoClose={2000} position="top-center" /> <Navbar bg="primary" variant="dark"> <Navbar.Brand href="#home">Firebase notifictations with React and Express</Navbar.Brand> </Navbar> <Container className="center-column"> <Row> <Col> <Messaging /> </Col> </Row> </Container> </Fragment> );
};
export default App;

This is a regular react component styled with react-bootstrap. There’s a toast component right at the top of our app, which we shall use to display notifications. Note that we also set the baseURL for the axios library. Everything of note happens inside the <Messaging /> component. Let’s now take a look at its content.

Open up client/src/Messaging.js and inspect the content.

export const Messaging = () => { const [messages, setMessages] = React.useState([]); const [requesting, setRequesting] = React.useState(false); React.useEffect(() => { setRequesting(true); axios.get("/messages").then((resp) => { setMessages(resp.data.messages); setRequesting(false); }); }, []); return ( <Container> {/* form goes here */} <div className="message-list"> <h3>Messages</h3> {requesting ? ( <Spinner animation="border" role="status"> <span className="sr-only">Loading...</span> </Spinner> ) : ( <> {messages.map((m, index) => { const { name, message } = m; return ( <div key={index}> {name}: {message} </div> ); })} </> )} </div> </Container> );
};

We have two state variables, messages and requesting. messages represent the list of messages from our database and requesting is for toggling our loader state. We have a React.useEffect block where we make our API call to the /messages endpoint and set the returned data in our messages state.

In the return statement, we map over the messages and display the name and message fields. On the same page, we include a form for creating new messages.

<Formik initialValues={{ name: "", message: "", }} onSubmit={(values, actions) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); actions.setSubmitting(false); toast.success("Submitted succesfully"); }, 1000); }}
> {(prop) => { const { handleSubmit, handleChange, isSubmitting } = prop; return ( <> <InputGroup className="mb-3"> <InputGroup.Prepend> <InputGroup.Text id="basic-addon1">Name</InputGroup.Text> </InputGroup.Prepend> <FormControl placeholder="Enter your name" onChange={handleChange("name")} /> </InputGroup> <InputGroup className="mb-3"> <InputGroup.Prepend> <InputGroup.Text id="basic-addon1">Message</InputGroup.Text> </InputGroup.Prepend> <FormControl onChange={handleChange("message")} placeholder="Enter a message" /> </InputGroup> {isSubmitting ? ( <Button variant="primary" disabled> <Spinner as="span" size="sm" role="status" animation="grow" aria-hidden="true" /> Loading... </Button> ) : ( <Button variant="primary" onClick={() => handleSubmit()}> Submit </Button> )} </> ); }}
</Formik>

We’re using the Formik library to manage our form. We pass the <Formik /> component an initialvalues props, an onSubmit prop and the form component we want to render. In return, we get back some handy functions such as handleChange which we can use to manipulate our form inputs, and handleSubmit which we use to submit the form. isSubmitting is a boolean that we use to toggle the submit button state.

I encourage you to give formik a try. It really simplifies working with forms. We will replace the code in the onSubmit method later.

Let’s now implement the method that will request a browser’s permission and assign it a token.

To start using Firebase in the frontend, we have to install the Firebase JavaScript client library. Note that this is a different package from the firebase-admin SDK.

# install firebase client library
yarn add firebase

Create a file client/src/firebaseInit.js and add the following content.

import firebase from 'firebase/app';
import 'firebase/messaging'; const config = { apiKey: "API-KEY", authDomain: "AUTH-DOMAIN", databaseURL: "DATABASE-URL", projectId: "PROJECT-ID", storageBucket: "STORAGE-BUCKET", messagingSenderId: "MESSAGING-SENDER-ID", appId: "APP-ID"
}; firebase.initializeApp(config);
const messaging = firebase.messaging(); // next block of code goes here

The Firebase docs state that:

“The full Firebase JavaScript client includes support for Firebase Authentication, the Firebase Realtime Database, Firebase Storage, and Firebase Cloud Messaging.”

So here, we import only the messaging feature. At this point, you could refer to the section on creating a Firebase project to get the config object. We then initialize Firebase and export the messaging feature. Let’s add in the final block of code.

export const requestFirebaseNotificationPermission = () => new Promise((resolve, reject) => { messaging .requestPermission() .then(() => messaging.getToken()) .then((firebaseToken) => { resolve(firebaseToken); }) .catch((err) => { reject(err); }); }); export const onMessageListener = () => new Promise((resolve) => { messaging.onMessage((payload) => { resolve(payload); }); });

The requestFirebaseNotificationPermission function requests the browser’s permission to send notifications and resolves with a token if the request is granted. This is the token that FCM uses to send a notification to the browser. It is what triggers the prompt you see on browsers asking for permission to send a notification.

The onMessageListener function is only invoked when the browser is in the foreground. Later, we will write a separate function to handle the notification when the browser is in the background.

Open up client/src/App.js and import the requestFirebaseNotificationPermission function.

import { requestFirebaseNotificationPermission } from './firebaseInit'

Then inside the App function, add the below code before the return statement.

requestFirebaseNotificationPermission() .then((firebaseToken) => { // eslint-disable-next-line no-console console.log(firebaseToken); }) .catch((err) => { return err; });

Once the app loads this function runs and requests the browser’s permission to show notifications. If the permission is granted, we log the token. In a production app, you should save the token somewhere that your backend can access. But for this tutorial, we’re just going to copy and paste the token into the back-end app.

Now run your app and you should see the notification request message. Click allow and wait for the token to be logged to the console. Since you’ve granted the browser permission, if we refresh the page you won’t see the banner anymore, but the token will still be logged to the console.

App request to show notifications
App request to show notifications. (Large preview)

You should know that Firefox browser (v75) doesn’t ask for notification permission by default. The permission request has to be triggered by a user-generated action like a click.

This is a good point for me to commit my changes. The corresponding branch is 04-request-permission.

Let’s now complete the code for saving a message to our database.

Open up client/src/Messaging.js and replace the onSubmit function of our form with the below code.

onSubmit={(values, actions) => { axios .post("/messages", values) .then((resp) => { setMessages(resp.data.messages.concat(messages)); actions.setSubmitting(false); toast.success("Submitted succesfully"); }) .catch((err) => { console.log(err); toast.error("There was an error saving the message"); });
}}

We make a post request to the /messages endpoint to create a new message. If the request succeeds we take the returned data and put it at the top of the messages list. We also display a success toast.

Let’s try it out to see if it works. Start the front-end and back-end servers. Before trying out the post request, open server/src/controllers/messages.js and comment out the line where we’re sending the notification.

# this line will throw an error if tokens is an empty array comment it out temporarily
// sendNotificationToClient(tokens, notificationData);

Try adding some messages to the database. Works? That’s great. Now uncomment that line before continuing.

Copy the notification token from the developer console and paste it into the tokens array. The token is a very long string, as shown below.

 const tokens = [ 'eEa1Yr4Hknqzjxu3P1G3Ox:APA91bF_DF5aSneGdvxXeyL6BIQy8wd1f600oKE100lzqYq2zROn50wuRe9nB-wWryyJeBmiPVutYogKDV2m36PoEbKK9MOpJPyI-UXqMdYiWLEae8MiuXB4mVz9bXD0IwP7bappnLqg', ];

Open client/src/Messaging.js, import the onMessageListener and invoke it just under the useEffect block. Any position within the function is fine as long it’s before the return statement.

import { onMessageListener } from './firebaseInit'; React.useEffect(() => { ... }, []); onMessageListener() .then((payload) => { const { title, body } = payload.data; toast.info(`${title}; ${body}`); }) .catch((err) => { toast.error(JSON.stringify(err)); });

The listener returns a promise which resolves to the notification payload on success. We then display the title and body in a toast. Note that we could have taken any other action once we receive this notification but I’m keeping things simple here. With both servers running, try it out and see if it’s working.

Works? That’s great.

In case you run into problems, you could always compare with my repo. The corresponding branch at this point is 05-listen-to-notification.

There’s just one bit we need to take care of. Right now we can only see notifications when the browser is in the foreground. The point about notifications is that it should pop up whether the browser is in the foreground or not.

If we were to be sending a display message i.e. we included a notification object in our notification payload, the browser will take care of that on its own. But since we’re sending a data message, we have to tell the browser how to behave in response to a notification when our browser is in the background.

To handle the background notification we need to register a service worker with our front-end client.

Create a file client/public/firebase-messaging-sw.js and enter the following content:

importScripts('https://www.gstatic.com/firebasejs/7.14.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.14.2/firebase-messaging.js'); const config = { apiKey: "API-KEY", authDomain: "AUTH-DOMAIN", databaseURL: "DATABASE-URL", projectId: "PROJECT-ID", storageBucket: "STORAGE-BUCKET", messagingSenderId: "MESSAGING-SENDER-ID", appId: "APP-ID"
}; firebase.initializeApp(config);
const messaging = firebase.messaging(); messaging.setBackgroundMessageHandler(function(payload) { console.log('[firebase-messaging-sw.js] Received background message ', payload); const notificationTitle = payload.data.title; const notificationOptions = { body: payload.data.body, icon: '/firebase-logo.png' }; return self.registration.showNotification(notificationTitle, notificationOptions);
}); self.addEventListener('notificationclick', event => { console.log(event) return event;
});

At the top of the file, we’re importing the firebase-app and the firebase-messaging libraries since we only need the messaging feature. Don’t worry if the import syntax is new. It’s a syntax for importing external scripts into service worker files. Make sure that the version being imported is the same as the one in your package.json. I’ve run into issues that I solved by harmonizing the versions.

As usual, we initialize Firebase, then we invoke the setBackgroundMessageHandler, passing it a callback, which receives the notification message payload. The remaining part of the code specifies how the browser should display the notification. Notice that we can also include an icon to display as well.

We can also control what happens when we click on the notification with the notificationclick event handler.

Create a file client/src/serviceWorker.js and enter the below content.

export const registerServiceWorker = () => { if ('serviceWorker' in navigator) { navigator.serviceWorker .register('firebase-messaging-sw.js') .then(function (registration) { // eslint-disable-next-line no-console console.log('[SW]: SCOPE: ', registration.scope); return registration.scope; }) .catch(function (err) { return err; }); }
};

This function registers our service worker files. Note that we have replaced the more detailed version generated by React. We first check if the serviceWorker is present in the navigator object. This is simple browser support. If the browser supports service workers, we register the service worker file we created earlier.

Now open client/src/index.js, import this function, and invoke it.

# other imports import { registerServiceWorker } from './serviceWorker' ReactDOM.render( ...
); registerServiceWorker()

If all goes well, you should see the service worker’s scope logged to your console.

Open http://localhost:3000/messaging in a second browser and create a message. You should see the notification from the other browser come into view.

Background and foreground notifications
Background and foreground notifications. (Large preview)

With that, we’ve come to the end of this tutorial. The corresponding branch in my repo is 06-handle-background-notification.

Conclusion

In this article, we learned about the different types of notification messages we can send with the Firebase Cloud Messaging (FCM). API. We then implemented the “data message” type on the backend. Finally, we generated a token on the client app which we used to receive notification messages triggered by the back-end app. Finally, we learned how to listen for and display the notification messages when the browser is in either the background or foreground.

I encourage you to take a look at the FCM docs to learn more.

Smashing Editorial (ks, ra, yk, il)