close icon
Next

Next.js Practical Introduction: Styling and Theming

Learn how to handle styling and theming while using Next.js to build server-side rendered React applications.

July 23, 2019

In this tutorial series, you'll be provided with a practical introduction to how Next.js can help you build web applications.

Created by Vercel, a cloud company for hosting frontends and serverless functions, Next.js is a React framework capable of creating applications that run both on the client and the server, otherwise known as Universal JavaScript applications. This framework helps you build universal apps faster by streamlining basic features like client-side routing and page layout while simplifying advance features like server-side rendering and code splitting.

In the first part of the series, you learned how to create pages and layouts. In this second, you'll learn how to apply styles to pages and create a theme using Next.js 9, the most recent framework version at the time of writing. Familiarity with the React library is recommended.

Setting Up the Project

If you are starting from this part, you can get the app that was built in the previous, first part by cloning the repo and checking out the part-1 branch:

git clone git@github.com:auth0-blog/whatabyte-next-intro.git whatabyte
cd whatabyte
git checkout part-1

If you haven't connected to GitHub using SSH, please clone the repo with the HTTPS URL:

git clone https://github.com/auth0-blog/whatabyte-next-intro.git whatabyte
cd whatabyte
git checkout part-1

The repository is cloned into the directory called whatabyte on your local machine. If the directory doesn't exist, it is created.

With the branch checked out, proceed to install the project dependencies:

npm install

Finally, execute the following command in your terminal to run the app:

npm run dev

To see the app in action and start following the tutorial, visit http://localhost:3000 on your browser.

Styling Components the Next.js Way

The application created in the first part of the series uses the style property of React components to specify component styling through JavaScript objects. That's handy but it won't let you create global CSS rules for your application. What you can do instead is to follow the CSS-in-JS component styling strategy that Next.js implements natively.

Next.js is preloaded with a CSS-in-JS framework called styled-jsx. This framework offers full, scoped, and component-friendly CSS support for JSX rendered on the client and/or the server-side. This lets you write scoped CSS rules within your JSX component templates. The style defined for a component only affects that component and nothing else — not even its children!

To see this in practice, first convert the Header component from using a style object to using styled-jsx. Open components/Header.js and update the component template as follows:

// components/Header.js

const Header = () => (
  <div className="Header">
    HEADER
    <style jsx>{`
      background-color: blue;
      color: white;
      width: 100%;
      height: 50px;
    `}</style>
  </div>
);

export default Header;

To use styled-jsx, you need to add the style jsx tag as a sibling or child of the JSX template element that you want to modify. The template of Header only has one element, thus, adding the style jsx tag as a sibling makes the most sense.

As an alternative, you can render an array of components or use React fragments.

CSS rules are written inside a template string that is placed within the style jsx tag. You write the rules the same way that you'd write them in a regular CSS file following any valid naming convention you prefer — as long as you are consistent — and terminating them with a semicolon.

The downside of using a template string to write CSS rules is that you lose IDE and code editor assistance to write them, for example. There's a candid debate about whether or not React developers should use CSS-in-JS.

The articles "What’s Wrong with CSS-in-JS?" by Brad Frost and "Bridging the Gap Between CSS and JavaScript: CSS-in-JS" by Matija Marohnić can help you form your own opinions about the topic.

The engineers that created Next.js suggest using CSS-in-JS to avoid practical issues that come with traditional CSS-file-based styling — especially in relation to the server-side rendering of React.

As an exercise, convert the NavBar and Layout components to using style jsx. When you are done, come back and compare your results:

// components/NavBar.js

const NavBar = () => (
  <div className="NavBar">
    NAVBAR
    <style jsx>{`
      background-color: red;
      color: white;
      width: 100%;
      height: 60px;
    `}</style>
  </div>
);

export default NavBar;
// components/Layout.js

import Head from "next/head";

import Header from "./Header";
import NavBar from "./NavBar";

const Layout = props => (
  <div className="Layout">
    <Head>
      <title>WHATABYTE</title>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta charSet="utf-8" />
    </Head>
    <Header />
    <div className="Content">{props.children}</div>
    <NavBar />
    <style jsx>{`
      .Layout {
        display: flex;
        flex-direction: column;
        height: 100%;
        width: 100%;
      }

      .Content {
        flex: 1;
        display: flex;
        flex-direction: column;
      }
    `}</style>
  </div>
);

export default Layout;

How did you do? Notice that you can use the classes you specified for each template tag within the style jsx template string. This makes it easier and more natural to style a more complex component.

Forgetting to change a comma to a semicolon will break the whole styling.

The flexible layout defined within the Layout component has a problem though: The .Content container doesn't stretch out to fill the space left by the Header and NavBar components. For the .Content container to enforce its flex: 1 style property, it needs to have a frame of reference for sizing coming from the elements that enclose it, in this case the .Layout container.

The height of the .Layout container is specified to be 100%; however, this container is not filling out the full height of the viewport. In turn, this could only mean that its enclosing container doesn't have a height of 100%.

Inspecting the structure of the Index page using your browser developer tools will reveal that the top-level container for the app is a div element with the ID __next. That element has no height or width; therefore, its descendant elements have no height value to inherit and reference for dynamic sizing.

To fix this, you need to assign the #__next container a height and width of 100% with one caveat: Anything within the pages subdirectory represents a web page of your front-end application. Next.js wants you to think about these pages as complete and self-sufficient. This means that every page knows how to render its content correctly independently of any other page.

The solution to this layout problem is then to tell each page in your application how to render the html, body and #__next elements. The best place to do this is by defining global styles in the existing style jsx tag of your Layout component template.

Open and update Layout.js with the following code that defines the same global styles present in the index.css file of a standard create-react-app application:

// components/Layout.js

import Head from "next/head";

import Header from "./Header";
import NavBar from "./NavBar";

const Layout = props => (
  <div className="Layout">
    <Head>
      <title>WHATABYTE</title>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta charSet="utf-8" />
    </Head>

    <Header />
    <div className="Content">{props.children}</div>
    <NavBar />
    <style jsx global>{`
      * {
        box-sizing: border-box;
      }

      html,
      body,
      #__next {
        height: 100%;
        width: 100%;
      }

      body {
        margin: 0;
        padding: 0;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
          "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
          "Helvetica Neue", sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
      }

      .Layout {
        display: flex;
        flex-direction: column;
        height: 100%;
        width: 100%;
      }

      .Content {
        flex: 1;
        display: flex;
        flex-direction: column;
        font-family: Arial;
      }
    `}</style>
  </div>
);

export default Layout;

You added the global keyword after the style jsx tag name. As the word may infer, adding global to the tag lets you define CSS rules that affect the whole application.

Save your code and look at the browser. Now, the layout flows nicely and the content container adapts to the space left by the header and navigation bar.

I think that learning how to layout components effectively is one of the most important aspects of front-end development. For that reason, I felt it was important to go over styling and laying out DOM elements before going into topics like routing.

The Layout component is starting to look bloated and the CSS-in-JS is hard to write and even indent properly. To make styling and creating global style classes easier, you'll switch to using SCSS stylesheets in the application.

"Learn how to use styled-jsx with Next.js to style components."

Tweet

Tweet This

Styling Next.js Applications using SCSS Files

To use .scss files you need extra modules available through plugins. These modules configure sensible defaults for server rendered applications.

In the terminal, run the following to install the Next.js SASS plugin:

npm install --save @zeit/next-sass node-sass

SCSS stylesheets are compiled to .next/static/css. Next.js automatically adds the .css files to the HTML. In production, a chunk hash is added so that styles can be updated when a new version of the stylesheet is deployed.

Create a next.config.js under the whatabyte directory:

touch next.config.js
  • Windows PowerShell:
ni next.config.js

Populate this file as follows:

// next.config.js

const withSass = require("@zeit/next-sass");
module.exports = withSass();

The next.config.js file is a regular Node.js module that lets you create custom Next.js configuration used by the server to build the application. To make the server aware of this new custom configuration you need to restart the server. Go to the terminal tab or window running the server, stop it, and execute npm run dev again.

Now, under the components directory, create an SCSS file named Header.scss:

touch components/Header.scss
  • Windows PowerShell:
ni components/Header.scss

Fill the Header.scss file with this:

// components/Header.scss

.Header {
  display: flex;
  justify-content: flex-start;
  align-items: center;

  height: 50px;
  width: 100%;

  padding: 0 20px;

  font-family: Arial, sans-serif;
  font-size: 24px;
  font-weight: bold;
  color: white;
  text-transform: uppercase;

  background: crimson;

  cursor: pointer;
}

Open, Header.js, delete the style jsx tag and import the SCSS stylesheet as follows:

// components/Header.js

import "./Header.scss";

const Header = () => <div className="Header">HEADER</div>;

export default Header;

Save your changes and look at the browser. New CSS rules make the Header component look better. The Header.js file also looks lighter. It may be easier for you to reason about styling by using SCSS files; however, if you want, feel free to continue this tutorial using CSS-in-JS if you liked that strategy better.

To add SCSS stylesheets for NavBar and Layout, create the NavBar.scss and Layout.scss files respectively under the components directory:

touch components/NavBar.scss components/Layout.scss
  • Windows PowerShell:
ni components/NavBar.scss, components/Layout.scss

Populate these files as follows:

// components/NavBar.scss

.NavBar {
  display: flex;
  justify-content: space-around;
  align-items: center;

  height: 60px;
  width: 100%;
  padding: 10px 0;

  background: crimson;

  font-family: Arial, sans-serif;
  font-size: 22px;
  color: white;

  box-shadow: 0px -2px 15px rgba(50, 50, 50, 0.45);

  a {
    color: inherit;
    text-decoration: none;
  }

  .active {
    color: navy;
  }
}
// components/Layout.scss

.Layout {
  flex: 1;
  display: flex;
  flex-direction: column;

  height: 100%;
  width: 100%;

  .Content {
    flex: 1;
    display: flex;
    flex-direction: column;
  }
}

Now, import each stylesheet into its respective component while deleting the CSS-in-JS logic:

// components/NavBar.js

import "./NavBar.scss";

const NavBar = () => <div className="NavBar">NAVBAR</div>;

export default NavBar;
// components/Layout.js
import Head from "next/head";

import Header from "./Header";
import NavBar from "./NavBar";

import "./Layout.scss";

const Layout = props => (
  <div className="Layout">
    <Head>
      <title>WHATABYTE</title>
      <meta name="viewport" content="width=device-width, initial-scale=1" />
      <meta charSet="utf-8" />
    </Head>

    <Header />
    <div className="Content">{props.children}</div>
    <NavBar />
  </div>
);

export default Layout;

However, the layout is broken again since you've lost the definition of CSS global rules. Not to worry, create an index.scss file under the pages subdirectory and populate it as follows:

touch pages/index.scss
  • Windows PowerShell:
ni pages/index.scss
// pages/index.scss

* {
  box-sizing: border-box;
}

html, body, #__next {
  height: 100%;
  width: 100%;
}

body {
  margin: 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
  "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
  sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
  monospace;
}

#root {
  display: flex;
  flex-direction: column;
}

Import this index.scss into the Index component that lives in pages/index.js:

// pages/index.js

import Layout from "../components/Layout";

import "./index.scss";

const Index = () => <Layout>Welcome to WHATABYTE!</Layout>;

export default Index;

Refresh the page and observe how the interface layout is fixed again. However, is this the best approach to managing global application styling?

It was discussed earlier that an architecture principle of Next.js is to create pages that are complete and self-sufficient. JavaScript files inside the pages directory represent a page or view of the application. If you add other pages, you'd need to import that index.scss file into them for the layout to show correctly. Thus, a better solution is to move index.scss from pages to the components directory and have the Layout component import it.

First, you must delete the index.scss import from index.js to avoid page build errors.

Then, move the index.scss stylesheet to the components directory:

mv pages/index.scss components/index.scss

Finally, update components/Layout.js as follows:

// components/Layout.js

// Other imports

import "./Layout.scss";
import "./index.scss";

// Layout component definition

export default Layout;

Refresh the page and the layout should look correct.

You could copy the content of index.scss and paste it into Layout.scss; however, it's better to keep those global styles on their own file. If you ever need to use another component as the core layout, you then simply import that file into that component. The filename also helps you quickly locate it among the other files.

And with that, you are done learning how to style Next.js components! It was a long journey but it's well worth it to understand how CSS works in any application as it's the core engine to position and structure anything on a web page.

What would you like to do... Next? What about creating a theme to make the styling maintenance of your app easier?

Creating a Theme for a Next.js Application using SCSS

Centralizing the styling of an application through a theme helps with brand consistency and improves application maintenance. Things like colors and fonts should be placed within SCSS partials that can be imported in component stylesheets. That way, if you need to change the hex value of a color, you only need to make the change in one place and the whole application becomes aware of that change.

Under the whatabyte directory, create a theme subdirectory:

mkdir theme

Create the following SCSS files under the theme subdirectory and populate their content accordingly:

touch theme/_fonts.scss theme/_colors.scss theme/_theme.scss
  • Windows PowerShell:
ni theme/_fonts.scss, theme/_colors.scss, theme/_theme.scss
  • _fonts.scss:
// theme/_fonts.scss

@import url('https://fonts.googleapis.com/css?family=Raleway:800|Merriweather+Sans|Share+Tech+Mono');

$logo-font: 'Share Tech Mono', monospace;
$header-font: 'Raleway', sans-serif;
$core-font: 'Merriweather Sans', sans-serif;
  • _colors.scss:
// theme/_colors.scss

$primary: #FFFFFF;
$primary-light: #ffffff;
$primary-dark: #cccccc;

$secondary: #2a3747;
$secondary-light: #546172;
$secondary-dark: #011120;

$on-primary: #000;
$on-primary-light: #A1A1A1;
$on-secondary: #fff;

$highlight: #FA4141;
  • _theme.scss:
// theme/_theme.scss

@import "./fonts";
@import "./colors";

SCSS files that start with an underscore, _, are named partials and can be imported into other SCSS files. What you'll do next is import the _theme.scss partial into other SCSS component files and use its values. Update the following files under the components directory:

  • Header.scss
// components/Header.scss

@import "../theme/theme";

.Header {
  display: flex;
  justify-content: flex-start;
  align-items: center;

  height: 50px;
  width: 100%;

  padding: 0 20px;

  font-family: $logo-font;
  font-size: 24px;
  font-weight: bold;
  color: $on-secondary;
  text-transform: uppercase;

  background: $highlight;

  cursor: pointer;
}
  • Layout.scss
// components/Layout.scss

@import "../theme/theme";

.Layout {
  flex: 1;
  display: flex;
  flex-direction: column;

  height: 100%;
  width: 100%;

  .Content {
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;

    width: 100%;

    background: $secondary;
    color: $on-secondary;
    font-family: $core-font;

    a {
      color: inherit;
    }
  }
}
  • NavBar.scss:
// components/NavBar.scss

@import "../theme/theme";

.NavBar {
  display: flex;
  justify-content: space-around;
  align-items: center;

  height: 60px;
  width: 100%;
  padding: 5px 0;

  background: $primary;

  font-family: $core-font;
  font-size: 22px;
  color: $on-primary-light;

  box-shadow: 0px -2px 15px rgba(50, 50, 50, 0.45);

  a {
    color: inherit;
    text-decoration: none;
  }

  .active {
    color: $highlight;
  }
}

The application is starting to look more polished and professional.

Next.js application looking more polished with styling and layout

"Learn how to create a theme for a Next.js app using SCSS"

Tweet

Tweet This

With the theme in place, you should replace the generic "HEADER" title of the app with "> WHATABYTE". Where do you think is the best place to pass that string as a prop?

As the Layout component is responsible for rendering the Header component, it should pass the title of the app as a prop to the Header component. Open components/Layout.js and update it as follows:

// components/Layout.js

import Head from "next/head";

import Header from "./Header";
import NavBar from "./NavBar";

import "./Layout.scss";
import "./index.scss";

const Layout = props => {
  const appTitle = `> WHATABYTE`;

  return (
    <div className="Layout">
      <Head>
        <title>WHATABYTE</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charSet="utf-8" />
      </Head>

      <Header appTitle={appTitle} />
      <div className="Content">{props.children}</div>
      <NavBar />
    </div>
  );
};

export default Layout;

The Layout function component now defines appTitle within its body and passes its value as the appTitle prop to the Header component. The header title should also link to the root page to create a better user experience. Naturally, update components/Header.js as follows:

// components/Header.js
import Link from "next/link";

import "./Header.scss";

const Header = props => (
  <Link href="/">
    <div className="Header">{props.appTitle}</div>
  </Link>
);

export default Header;

Take a look at the browser and notice the new cool header title. If you ever want to change it, you'll just need to change the value of appTitle within the Layout component function. Layout acts as the data hydration point for the application user interface.

Next.js application polished layout running locally

"Learn how to polish Next.js apps using SCSS to create themes."

Tweet

Tweet This

Recap

In this tutorial, part 2 of our series, you have learned how to apply styles to pages using the styled-jsx CSS-in-JS framework that comes bundled with Next.js. You also learned how to integrate Next.js with SCSS to apply styles and create a theme to serve as the look-and-feel foundation of your web app.

Notice in components/NavBar.scss the .active class embedded within the .NavBar class. What gives? This class would be useful to give a distinctive color to the active navigation UI element if you were using React Router v4 with a create-react-app project.

To achieve that result, you need to learn how to implement navigation, routing, and use React high-order components with Next.js, which is the topic of the next part!

Again, you can reference Part 1 for creating pages and layouts, here.

Continue to Part 3: Next.js Navigation and Routing

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon