Lessons learned from building landing pages
Tags: css, sass, beginners, webperf
Published: 2021-06-08 21:22:00

I recently took the time to improve my [portfolio page](https://toluagboola.github.io/), and I also had an opportunity to build a [landing page](https://stellar.freshman.tech/) for an [open source project](https://github.com/ayoisaiah/stellar-photos). This gave me the opportunity to learn a CSS framework, and pick up a few other useful skills along the way. I was able to build and deploy both sites, and I'm now writing about the lessons I learned during the course of working on both pages.

## Choosing a framework

I made use of the [Bulma framework](https://bulma.io/) for both projects. I chose Bulma because it's based on Flexbox, has no JavaScript (which I didn't need for either website) and is built with Sass which I wanted to learn. The class names in Bulma are also easy to understand and remember. It was amazing how much faster I was able to go with a framework. When I built my old portfolio page, I wrote the styles from start to finish and it took so long, but this time I didn't even spend up to half the time with much better results.

![My portfolio page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/65p6b8z9tn3vkp7jtsad.png)

One issue I had at first was that whenever I wrote custom styles, the framework styles would overwrite them. As a result, I found myself using `!important` a lot. I later realised that it was because of the order in which I linked them in my HTML. In CSS, when rules on the same element have the same specificity (classes in my case), the styles that come last will take precedence. To solve my problem and stop fighting with the framework, all I did was link my CSS file _after_ the Bulma stylesheet, like so:

```html
<link
	rel="stylesheet"
	href="https://cdn.jsdelivr.net/npm/bulma@0.9.2/css/bulma.min.css"
/>
<link rel="stylesheet" type="text/css" href="main.css" />
```

As I progressed with my work, I realized that I was including the entire Bulma framework in my project, but I only used a small part of it for the page which didn't feel right to me. I did some research and discovered that Sass provided a much better way for me to include only what I needed from the framework. First I had to install `bulma` through `npm`:

```
npm install bulma
```

This command created a `bulma` folder within the `node_modules` folder in my project directory. I was then able to remove the CDN link from my HTML file and import the Bulma components I needed using the path to their specific files:

```scss
@import "node_modules/bulma/sass/components/navbar.sass";
@import "node_modules/bulma/sass/elements/button.sass";
@import "node_modules/bulma/sass/elements/container.sass";
@import "node_modules/bulma/sass/elements/image.sass";
// ...and any other part of the framework I need
```

Doing this reduced the size of the CSS bundle in one of the projects from 240KB to 160KB. Minifying the code reduced the file size further to 137KB which should translate to faster loading times.

## Loading images

Whenever the pages were loading, I noticed that the images were causing the pages to shift because spaces were not reserved for them until they load which was not ideal. The solution was to set the `width` and `height` attributes for each image on the page so as maintain the layout of the page regardless of whether the images are loaded or not. With these attributes in place, layout shifting is prevented causing the page to behave more predictably.

```html
<img
	loading="lazy"
	width="1500"
	height="975"
	src="image.png"
	class="project-img"
/>
```

Finding out the specific dimensions of an image can be done through the browser DevTools. All you need to do is hover the cursor on the image's `src` link and a popup will provide more details. When doing this, it's important to use the **intrinsic size** which is the actual size of the image and not the rendered size which is dependent on the device.

![Image showing intrinsic and rendered image widths](https://res.cloudinary.com/dipyaby4f/image/upload/v1622997404/image-width.png)

Another way I was able to improve the image loading experience was by [lazy loading](https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading) them. That means instructing the browser to delay the loading of off-screen images until the user scrolls near them. To achieve that, set the `loading` attribute on the `img` tag to `lazy` and that's it.

```html
<img src="image.png" loading="lazy" />
```

## Learning Sass and CSS variables

To up my styling skills a notch, I decided to learn how to use a CSS Preprocessor ([SASS](https://sass-lang.com/)) and more advanced CSS features like variables (a.k.a. custom properties). I chose to learn Sass simply because it's the most popular one out there. It can be installed with this command:

```
npm install -g sass
```

To run Sass code in a web browser, it must first be transpiled to CSS. Every time a change is made in a `styles.scss` for example, the following command will compile it into `styles.css` which the browser can read. Link the compiled CSS file in your HTML file, and you're good to go.

```
sass --watch style.scss style.css
// Press `Ctrl + C` to cancel the operation
```

With Sass, I was able to make use of [mixins](https://sass-lang.com/documentation/at-rules/mixin) and nesting. For example, I had several flex containers in my application. Instead of declaring the same styles every single time, I created a mixin for it. The advantage is that it helped to reduce repetition, and kept my code cleaner and shorter. I also made use of CSS variables over Sass variables for the sole reason of being able to access and manipulate them with JavaScript if necessary.

```scss
:root {
	--bg-color: #ffffff; // CSS variable
}

// Mixin declaration
@mixin flex-container {
	display: flex;
	align-items: center;
	justify-content: space-between;
}

.project {
	background: var(--bg-color); // CSS variable in use
	@include flex-container; // Mixin in use
}
```

## Performance optimisation

Before deploying the sites to GitHub pages, I ran both of them through [Lighthouse](https://web.dev/measure/) to measure their performance. Lighthouse provides audit reports in several categories such as Accessibility, SEO etc. Here are the initial results for one of the sites:

![First results](https://res.cloudinary.com/dipyaby4f/image/upload/v1622975122/stellar-b4-score.png)

I followed some suggestions from the report which include:

- Adding `alt` attributes to all images.
- Sizing images properly with the `width` and `height` attributes.
- Eliminating [render-blocking resources](https://web.dev/render-blocking-resources/).

After improving my code based on the above recommendations and running the performance test again, my score on Accessibility went up from 75% to 94%. The remaining categories also went up in score:

![New results](https://res.cloudinary.com/dipyaby4f/image/upload/v1622975191/stellar-new-score.png)

While I was reading up on render-blocking resources, I realised that more than 90% of the CSS on my page was not being used despite my attempts to include only what I needed from Bulma. This insight was gained when I checked the [Coverage tab](https://developer.chrome.com/docs/devtools/coverage/) in the Chrome DevTools.

In the process of trying to figure out how to remove the unused code, I found out about [UnCSS](https://github.com/uncss/uncss), a tool that removes unused CSS from your stylesheets. It can be installed by running:

```
npm install uncss --save-dev
```

Then I created an `uncss.js` file and used the following code from the documentation:

```javascript
const uncss = require("uncss");
const fs = require("fs");

// Your html files here
const files = ["index.html"];

uncss(files, (err, output) => {
	try {
		if (err) throw err;

		// Write output to a css file here
		fs.writeFile("main.css", output, (err) => {
			if (err) throw err;
			console.log("uncss: done!");
		});
	} catch (err) {
		console.error(err);
	}
});
```

UnCSS analyses each HTML file in the `files` array and provides the trimmed css in the callback function (as `output`).  `fs.WriteFile` then creates a `main.css` file and appends the content of the `output`. The whole operation is wrapped in a `try...catch` block for efficient error handling. After saving the file, I updated the build script in my `package.json` file:

```json
"scripts": {
	"test": "echo \"Error: no test specified\" && exit 1",
	"build": "npx sass --style compressed main.scss main.css && node uncss.js"
},
```

When I run `npm run build`, it converts the Sass file to minified CSS code, and subsequently runs the UnCSS script to remove the unused parts of my code thereby increasing performance and reducing loading time. It absolutely changed the game for me after I used it. The size of the CSS file in one of the projects went from 137KB to 11KB representing approximately a 2,000% reduction from the original 240KB stylesheet size without any visual differences! This ensures that I'm sending the smallest possible bundle to the users.

![Coverage tab in Chrome DevTools](https://res.cloudinary.com/dipyaby4f/image/upload/v1623059812/coverage-tab.png)

## Takeaway

All in all, I learned a lot of new things during the process of building the both landing pages. They include:

- The value of CSS variables.
- Sass, its features and how to use them.
- Using only what is needed to increase performance.
- How to use the UnCSS tool to remove unused CSS code.
- Setting specific width and height for images to prevent layout shifting.
- Using Lighthouse to measure and improve site performance.

You can check and compare the old and new versions of the sites:

- [Old Stellar Photos website](http://stellar-website.surge.sh/) and [New Stellar Photos website](https://stellar.freshman.tech/).
- [Old personal portfolio page](http://tolu-portfolio.surge.sh/) and [New personal portfolio page](https://toluagboola.github.io/).

I hope you have gained some value from this article. Let me know your thoughts in the comments. 

Thanks a lot for reading!