[NextJS] Blog Redesign Journey - 3. Adding SCSS
[NextJS] Blog Redesign Journey - 3. Adding SCSS
Originally, my blog used the JS-in-CSS styling approach. The reason was Material-UI. Since Material-UI's official examples explicitly explain things using the JS-in-CSS approach, I, being at a beginner level with React, naturally assumed this was how it had to be done.
However, as development continued, some components were forced into complex styles, and components with bloated style syntax started to appear. As a result, the drawbacks of JS-in-CSS gradually became apparent. A representative problem is FOUC (Flash Of Unstyled Content) — a phenomenon where style rendering takes time, causing the user to see the page before rendering is complete. On my page, FOUC occurred for just under about a second, which severely hurt the user experience.
This FOUC issue was also the biggest reason I decided to redesign my blog, and after researching related information, I confirmed that CSS-in-CSS performs significantly better.
JAVASCRIPT
/** * Function that returns a styles object * * @returns {JSON} styles object */ function getStyles() { return makeStyles((theme) => ({ fab_bright: { position: "fixed", bottom: 50, right: 50, backgroundColor: grey[800], color: grey[200], "&:hover": { backgroundColor: grey[700] }, "& svg": { color: orange[600] }, [theme.breakpoints.up("md")]: { "& span": { marginLeft: theme.spacing(1) } }, [theme.breakpoints.down("sm")]: { bottom: 70, right: 20 } }, fab_dark: { position: "fixed", bottom: 50, right: 50, backgroundColor: grey[200], color: grey[900], "&:hover": { backgroundColor: grey[300] }, "& svg": { color: blue[600] }, [theme.breakpoints.up("md")]: { "& span": { marginLeft: theme.spacing(1) } }, [theme.breakpoints.down("sm")]: { bottom: 70, right: 20 } }, div: { height: 24 } }))(); }
Looking at the style implementation code from the JS-in-CSS days, elements were nested in a way that was hard to translate directly into plain CSS. Also, having become accustomed to the convenience that CSS nesting offers, I wanted to move the styles to CSS while keeping that same convenience.
In the end, I decided to apply a CSS preprocessor to the project.
There are several kinds of CSS preprocessors.
The ultimate goal of a CSS preprocessor is to secure various development advantages through the extensibility of CSS.
Using a CSS preprocessor lets you use dynamic coding features in a file, such as @for, @mixin, and variables. Unfortunately, preprocessor files can't be used directly in the browser as-is. Like other languages of this kind, compilation is required, and the output of compilation is a CSS file.
In other words, at the compilation stage, the statements declared in the CSS preprocessor file are executed and output as plain CSS.
- Reduces repetitive CSS syntax
- Batch management through variables
- Improved CSS syntax readability through nesting
- Easier componentization through file separation
- Each preprocessor has its own learning curve
- Requires setting up a separate development environment
For this blog, I adopted SCSS. It's because you can use a variety of extended syntax befitting a CSS preprocessor, while barely differing from existing CSS syntax.
The predecessor of SCSS was originally SASS, which stands for Syntactically Awesome Style Sheets. Loosely translated, it roughly means a style sheet that's syntactically awesome.
SASS was the very first preprocessor to appear, and its syntax was based on the Ruby language, which made it hard to switch directly over from CSS in many respects. The expressions were different, and there were differences in syntax as well.
Later, SCSS (Sassy CSS) came out to resolve SASS's drawbacks. It has the extensibility of a CSS preprocessor while being very similar to CSS syntax. Thanks to this, it has a considerably lower learning curve among preprocessors.
The difference between SCSS and SASS is clearly shown in the example below.
CSS
div { color: grey; width: 80px; height: 160px; } div h1 { color: dodgerblue; }
SASS
$length: 80px div color: grey width: $length height: $length * 2 h1 color: dodgerblue
SCSS
$length: 80px; div { color: grey; width: $length; height: $length * 2; h1 { color: dodgerblue; } }
As you can see from the difference above, SASS's expressions differ somewhat from CSS and SCSS. This difference arose because they were modeled after different languages.
Fortunately, SCSS, which came out later, absorbed both the advantages of SASS and the syntax of CSS, so writing it feels almost no different from writing CSS. As a result, most people tend to adopt SCSS more. In fact, anything that can be implemented in SASS can be fully implemented in SCSS, and even the official homepage recommends SCSS. Also, the development environment for SASS and SCSS is identical.
SASS is the pioneer of CSS preprocessors, but for various reasons, its successor SCSS is used more widely. However, because of this symbolic status and the similarity in names, SASS and SCSS are sometimes used interchangeably without distinction, or both are simply lumped together and referred to as SASS.
In other words, it's fine to think of it as SASS = SCSS.
Because of these various advantages, I decided to adopt SCSS.
Unlike Typescript, SCSS doesn't come with a separate template, so you have to configure it yourself. It's very easy, so there's no need to be intimidated.
BASH
# With NPM npm install @zeit/next-sass --save-dev # With Yarn yarn add @zeit/next-sass --dev
Use the command above to install the SASS Loader. You might ask, "Didn't I write SCSS?" As mentioned above, the development environment for SASS and SCSS is identical. Since the compiler is also the same, there's no problem installing the SASS Loader.
JAVASCRIPT
const withSass = require('@zeit/next-sass'); module.exports = withSass(); // existing next.config.js contents ...
Add the statement above to next.config.js, NextJS's configuration file. Inside withSass(), you can specify additional options in JSON form. For example, once the @zeit/next-sass loader is applied, it will throw an error telling you that all CSS files must be changed to SASS/SCSS. In that case, you can add the option below to allow CSS to be used alongside it.
JAVASCRIPT
module.exports = withSass({ cssModules: true });
You can check detailed options at the @zeit/next-sass NPM repository.
After this, using it in the project is the same as with CSS.
As a CSS preprocessor, SCSS provides powerful features that CSS lacks. As you explore it, you'll find it resolves a lot of frustrations you felt while coding CSS, or provides features you wished CSS had.
Variables in programming carry various meanings, but among them, one advantage is being able to assign and manage a specific value through a single variable. If you need to change that value and there's no variable, you'd have to change every piece of code that uses that value.
But if you use a variable, all you need to do is change the value assigned to the variable, and you're done.
CSS originally had no such feature, but thanks to the existence of variables, SCSS lets you approach CSS from a more programming-oriented perspective.
SCSS
$base: 16px; .font-1 { font-size: $base; } .font-2 { font-size: $base + 2px; } .font-3 { $color: dodgerblue; font-size: $base + 4px; background-color: $color; border: 1px solid $color; }
CSS
.font-1 { font-size: 16px; } .font-2 { font-size: 18px; } .font-3 { font-size: 20px; background-color: dodgerblue; border: 1px solid dodgerblue; }
This way, you can use variables through the $ symbol. You can not only assign values normally but also perform arithmetic operations. A variable can be assigned any value usable in CSS (#05A46B, skyblue, "Nanum Gothic", 38px, etc.).
SCSS's base is a global variable that can be called from anywhere. color is a local variable that can only be called within the .font-3 block and its nested sub-blocks.
🔍 Variable scope
SCSS variables have their own scope. A variable declared inside a block can be called from nested sub-blocks. Conversely, a variable declared in a sub-block cannot be called from the parent block. If declared not inside a block but directly in the file itself, it becomes a global variable that can be called anywhere declared in the file.
SCSS
.font-1 { $base: 16px !global; font-size: $base; } .font-2 { font-size: $base + 2px; } .font-3 { $color: dodgerblue; font-size: $base + 4px; background-color: $color; border: 1px solid $color; }
Besides declaring it outside a file, you can use the !global directive to declare a global variable anywhere. However, a global variable declared with !global inside a block is only accessible from code that comes after that block.
If the base variable were declared as a global variable inside .font-2, it would not be accessible from the .font-1 block.
You can declare not only simple values but also lists.
SCSS
$bright: #000000, #444444, #888888, #BBBBBB, #FFFFFF; $list: red, #FF00FF, "Arial", 16px;
Lists are separated by commas. The data types within a list don't need to be the same.
SCSS
$bright: #000000, #444444, #888888, #BBBBBB, #FFFFFF; // => #888888 nth($bright, 3); // => #888888 is replaced with #777777 set-nth($bright, 3, #777777); // => #EEEEEE is added to bright append($bright, #EEEEEE);
The basic syntax for lists is as shown above. You can also implement the foreach found in other languages using @each.
SCSS
$color: white, red, green, blue, black; @each $item in $color { .font-#{$item} { color: $item; } }
CSS
.font-white { color: white; } .font-red { color: red; } .font-green { color: green; } .font-blue { color: blue; } .font-black { color: black; }
Using @each, you can easily create repetitive statements.
While the list above consists of simple elements, a Map is a variable in the familiar key-value form.
SCSS
$map: (shorter: 20px, short: 40px, normal: 60px, long: 80px, longer: 100px); $map: (a: 20px, b: red, c: #00DE00, d: "Arial", e: center);
Map key-values are written as above. As with lists, the type of each element can be freely declared.
SCSS
$map: (shorter: 20px, short: 40px, normal: 60px, long: 80px, longer: 100px); // => 20px map-get($map, shorter); // => 100px of longer is replaced with 120px map-set($bright, longer, 120px); // => returns an array of the map's keys, in the order shorter, short, ..., longer map-keys($bright); // => returns an array of the map's values, in the order 20px, 40px, ..., 100px map-values($bright);
You can work with Maps as shown above.
A familiar conditional statement. SCSS lets you implement conditionals with the directives above, and they work very similarly to what we're used to.
SCSS
@mixin box($size, $platform) { width: $size; height: $size; @if $platform == "naver" { background-color: #03C75A; color: white; } @else if $platform == "kakao" { background-color: #FEE500; color: black; } @else { background-color: white; color: black; } } $box-size: 50px; .auth[data-platform=naver] { @include box($box-size, "naver"); } .auth[data-platform=kakao] { @include box($box-size, "kakao"); } .auth[data-platform=google] { @include box($box-size, "google"); }
CSS
.auth[data-platform=naver] { width: 50px; height: 50px; background-color: #03C75A; color: white; } .auth[data-platform=kakao] { width: 50px; height: 50px; background-color: #FEE500; color: black; } .auth[data-platform=google] { width: 50px; height: 50px; background-color: white; color: black; }
This way, you can apply SCSS differently depending on the value. You can build on this to add or exclude additional styles under specific conditions.
The usage is very similar to conditional statements you're already familiar with, so it shouldn't be difficult.
Every programming language provides at least one form of loop statement. SCSS provides it in the form of @for.
SCSS
$base-color: #036; @for $i from 1 through 3 { ul:nth-child(3n + #{$i}) { background-color: lighten($base-color, $i * 5%); } }
CSS
ul:nth-child(3n + 1) { background-color: #004080; } ul:nth-child(3n + 2) { background-color: #004d99; } ul:nth-child(3n + 3) { background-color: #0059b3; }
It can be used as shown above. $i is an arbitrarily named key index variable, and it iterates from 1 through 3.
Once you've written CSS for a while, at some point you'll want to apply a function-like concept from other languages.
Traditional CSS has no concept of functions, so to reuse the same code you either had to use the same selector or, unavoidably, resort to duplicated code.
But in SCSS, the @mixin syntax lets you store a snippet of code and use it wherever appropriate.
SCSS
@mixin square($size, $color) { width: $size; height: $size; background-color: $color; &:hover { background-color: transparent; border: 1px solid $color; } } .box { @include square(20px, red); box-shadow: 1px 1px 10px grey; }
CSS
.box { width: 20px; height: 20px; background-color: red; box-shadow: 1px 1px 10px grey; } .box:hover { background-color: transparent; border: 1px solid red; }
As shown above, we declared a snippet called square() using @mixin. This snippet takes the arguments size and color.
Calling that snippet via @include in the desired block includes the called snippet in that block. This effectively removes code duplication and lowers the difficulty of maintenance, and this pattern is also very useful for managing styles on a per-component basis.
When using @include, if the @mixin doesn't take any separate arguments, you can omit the parentheses.
@import lets you insert another SCSS file so you can write additional SCSS on top of that file's contents.
By managing shared or modularized SCSS as separate files and inserting the required module into the SCSS that needs it via @import, you can achieve modularization of SCSS.
SCSS
// box.scss @mixin square($size, $color) { width: $size; height: $size; background-color: $color; &:hover { background-color: transparent; border: 1px solid $color; } } .box { @include square(20px, red); box-shadow: 1px 1px 10px grey; }
SCSS
@import "./box.scss"; // require-box.scss .require-box { @include square(20px, dodgerblue); background-color: grey; }
CSS
/* require-box.css */ .box { width: 20px; height: 20px; background-color: red; box-shadow: 1px 1px 10px grey; } .box:hover { background-color: transparent; border: 1px solid red; } .require-box { width: 20px; height: 20px; background-color: dodgerblue; box-shadow: 1px 1px 10px grey; } .require-box:hover { background-color: transparent; border: 1px solid dodgerblue; }
After the @import directive, you just enter the path of the file to insert.
Let's assume we have an arbitrary SCSS file box.scss, and require-box.scss, which is written by inserting it. In the compiled result require-box.css, the contents of box.scss and require-box.scss are compiled together into a merged output, as shown above.
By inserting box.scss, require-box.scss can use the global variables or snippets declared in box.scss. However, there's a possibility of being unintentionally affected by content declared in box.scss, so be careful about this when designing.
SCSS
@mixin oneline { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; transition: 0.5s; } .category { @include oneline; width: 50%; font-size: 20px !important; margin-bottom: 0px !important; transition: 0.5s; color: map-get($map: $amber, $key: "700"); @media (max-width: 960px) { font-size: 16px !important; transition: 0.5s; } }
This is part of the SCSS that displays the category of a piece of content. Through @mixin and @include syntax, I was able to turn code into a function that could be called wherever needed.
SCSS
@mixin genColor($map, $str) { @each $key, $val in $map { .#{$str}-#{$key} { color: $val; } } } @include genColor($red, red); @include genColor($pink, pink); @include genColor($purple, purple); @include genColor($deepPurple, deepPurple); @include genColor($indigo, indigo); @include genColor($blue, blue); @include genColor($lightBlue, lightBlue); @include genColor($cyan, cyan); @include genColor($teal, teal); @include genColor($green, green); @include genColor($lightGreen, lightGreen); @include genColor($lime, lime); @include genColor($yellow, yellow); @include genColor($amber, amber); @include genColor($orange, orange); @include genColor($deepOrange, deepOrange); @include genColor($brown, brown); @include genColor($grey, grey); @include genColor($blueGrey, blueGrey);
@mixin is very similar in concept to methods in other languages, making it an attractive keyword for effectively preventing code duplication.
SCSS
@import "./fonts/apple.scss"; @import "./fonts/blacksword.scss"; @import "./common/color.scss"; @import "./common/icons.scss";
@import lets you insert other SCSS files. Using this pattern, I was able to manage SCSS on a per-component basis, preventing the code from growing too long and providing better maintainability.
For more detailed information, check the official SASS documentation.
Unlike Typescript, which had several minor drawbacks accompanying its big advantages, despite SCSS's many strengths, its drawbacks were barely noticeable.
I feel it improved development convenience and productivity so much that I want to apply SCSS to all my future projects.
Even the need to set up an SCSS development environment turned out to be a non-issue in the end, since my blog is built with NextJS anyway — it was resolved just by adding a few lines to the config. All in all, it was a very satisfying experience.
![[NextJS] Blog Reorganization Journal - 2. Clothing It in TypeScript](https://user-images.githubusercontent.com/50317129/134931033-89954c3d-5e00-4b3b-85aa-54a1dfa29e46.png)
![[OAuth2.0] Building an OAuth2.0 Authorization Server with ScribeJAVA - 1. What is OAuth2.0?](https://user-images.githubusercontent.com/50317129/137171016-99af1db1-a346-4def-9329-6072b927bdc0.png)