DEV Community

Cover image for Styled Components 101 πŸ’… Lecture 3: SSR with Next.js + Custom Icon Fonts 😍
_CODE
_CODE

Posted on

Styled Components 101 πŸ’… Lecture 3: SSR with Next.js + Custom Icon Fonts 😍

Hello, everybody! πŸ‘‹
And welcome to the 3rd lecture of the Styled Components 101 series πŸ™Œ

In this lecture, we'll be covering:

1️⃣ How we can use Styled Components within a Next.js configuration.

2️⃣ How to use custom icon fonts within our styled components.

If you're new to Styled Components and this lecture is the first one you run into, I suggest taking a look at the previous lectures first, where we covered some basic concepts and examples of Styled Components.

With all this said, let's move on to today's topic πŸ‘‡

How to get Styled Components to work if we're using Next.js 😺

Let's first see what happens if no configuration for Styled Components has been defined for our Next.js project and we try to use the library.

To start off, we're going to create a StyledButton component (already known to all at this point 😜) and render it within the main component of our app.

StyledButton.js

import styled from "styled-components";

export default styled.button`
    background-color: ${props => props.bg};
    color: ${props => props.color};
    border: none;
    border-radius: 5px;
    padding: 20px;
    font-size: 1.5rem;
`
Enter fullscreen mode Exit fullscreen mode

index.js

import StyledButton from '../components/StyledButton';

const Home = () => {
   return(
      <StyledButton bg="#c64dff" color="#fff">Styled Button in Next.js</StyledButton>
   )
}
Enter fullscreen mode Exit fullscreen mode

If we run our app, this is the resultant button:

Alt Text

Where in the world are our styles? πŸ€” Let's find out what's going on in here.

First, if we go to the Console Tab in the browser's dev tools, we see that something is throwing an error:

Alt Text

The error reads:

_Warning: Prop `classname` did not match. Server: "sc-pNWdM kcTaxf" Client: "sc-bdnxRM gSuzZs" at button...
Enter fullscreen mode Exit fullscreen mode

It seems like two different classes are being assigned on the server and the client, resulting in an inconsistency.

Let's now have a look at the Elements tab:

Alt Text

Our button is there and we can confirm that the class provided by Styled Components has been assigned correctly, but the styles are completely missing.

So, what can we do to solve this? 😩

Well, this is neither a bug nor even a big deal. It's just that a further configuration is required by Next.js to get to work Styled Components in our project in order to use it.

So, first, we're going to install the babel-plugin-styled-components, which is required for SSR (Server Side Rendering).

npm install babel-plugin-styled-components
Enter fullscreen mode Exit fullscreen mode

Now, let's create a .babelrc file if we haven't already done so (it's not created by default when creating a Next.js app) and write the following configuration for the newly installed plugin on it:

On the terminal:

touch .babelrc
Enter fullscreen mode Exit fullscreen mode

.babelrc

{
   "presets": ["next/babel"],
   "plugins": [["styled-components", {"ssr": true, "preprocess": false}]]
}
Enter fullscreen mode Exit fullscreen mode

But we're not done yet, we still need a little bit more of configuration.

Now we need to inject the server side rendered styles in the <head> element of our HTML file. For this purpose, we need to override the Document file, which is provided by Next.js.

The Document file is extendable, which means that we can add content to it if needed, and it's mainly used to add custom content to the <html> and <body> elements of the HTML main file. Note that this file is only rendered on the server.

This document is automatically generated with the creation of the Next.js app, but since we need to extend it, we're going to create another file called _document.js to override the original one. This new file should be placed within the /pages directory and it will look like this πŸ‘‡

_document.js

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet()
    const originalRenderPage = ctx.renderPage

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) =>
            sheet.collectStyles(<App {...props} />),
        })

      const initialProps = await Document.getInitialProps(ctx)
      return {
        ...initialProps,
        styles: (
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>
        ),
      }
    } finally {
      sheet.seal()
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Note that renderPage should only be modified when working with CSS-in-JS libraries, like Styled Components, since they need the app to be wrapped to work on server side. Otherwise the default configuration should always remain πŸ‘

If we're not planning to use any of these libraries, the following configuration could be a good starting point if we need to add something to the structure of our HTML document, being able to remove all that we don't need to change (note that we're overriding), like getInitialProps or even the render method:

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx)
    return { ...initialProps }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

Enter fullscreen mode Exit fullscreen mode

In any other case, there's no need to extend the original Document and we can forget about it 😜.

Once we've made all of these arrangements, let's re-run our application and see what happens!

Alt Text

There we go! Our Styled Button is finally rendering properly πŸ‘

And that would be all the configuration needed to work with Styled Components + Next.js.

Let's now dive into how we can add a custom icon font to a styled component πŸ‘‡

Custom Icon Fonts in Styled Components ❀️

This topic is totally separate from the previous one, since an extra configuration for fonts is no longer required in Next.js, but anyway, let's extend our styled button by adding an icon from a custom icon font and let's see what we need to do to make it work.

First of all... What is an icon font? πŸ”

Unlike regular fonts, which contain letters and numbers, an icon font is nothing more than a collection of symbols and glyphs that works as a typeface. Its use is widely extended because they are really easy to style with CSS.

The tool we're going to use to get our icon font is Icomoon, but this example works for every downloaded fonts coming from any font resource.

Let's say we have already downloaded our font files after generating the custom set and we're all set and ready to go.

Integrating a custom icon font set into our project 🐸

What we're going to do in this section is to add an icon from our custom icon font as an ::after pseudo-element, to place it after the text of our button.

So, first, we're going to add a new prop to our styled button call and pass it the content value of an icon of our choice.

Note that every icon has a sort of id, which is the value we'll pass in to the prop named icon. This content value is always provided by the tool, so you don't need to assign it yourself.

In this case, the content value for our icon is \e900.

<StyledButton bg="#c64dff" color="#fff" icon="\e900">Styled Button in Next.js</StyledButton>
Enter fullscreen mode Exit fullscreen mode

Then, we'll just add the ::after pseudo-element to the StyledButton definition:

import styled from "styled-components";

export default styled.button`
    background-color: ${props => props.bg};
    color: ${props => props.color};
    border: none;
    border-radius: 5px;
    padding: 20px;
    font-size: 1.2rem;
    &::after{
        font-family: "icomoon";
        content: "${props => props.icon}";
        padding-left: 8px;
    }   
`
Enter fullscreen mode Exit fullscreen mode

Time to create a global style ⚑️

In the previous lecture, we had a glimpse on how to create a global style, as part of the example where we created a light/ dark theme toggler. So don't hesitate to take a look at it for further reference if needed πŸ˜€

But in case you missed it or you don't have the time to read one more article, keep reading: everything's explained ahead ✌️

First, we are going to create our global styles file, that will be called IconFont.js, and which will host the CSS definition to import custom fonts. It's just plain CSS inside a styled component. Nothing new 😎 And it will look like this:

IconFont.js

import { createGlobalStyle } from "styled-components";

export default createGlobalStyle`
@font-face {
   font-family: "icomoon";
   src: url("/fonts/icomoon.eot");
   src: url("/fonts/icomoon.eot?#iefix")
   format("embedded-opentype"),
   url("/fonts/icomoon.svg#icomoon") format("svg"),
   url("/fonts/icomoon.woff") format("woff"),
   url("/fonts/icomoon.ttf") format("truetype");
};
`
Enter fullscreen mode Exit fullscreen mode

Things to consider at this point

  1. Pay attention to the routes and the filenames: the ones you're seeing above work for the configuration that we're going to see in a minute. You should always use the actual routes of your project and the names you provided to your font files. Otherwise, it won't work ❌
    It may sound obvious but sometimes it happens that we make a mistake in writing this definition and we go nuts for the rest of the day trying to figure out what's going on. Believe me, it happens more often that you may think 😝

  2. In case you're using a theme, you're supposed to already have a global styles file. In such case, just add the @font-face definition to it and you'd be set and done.

Then, how do I have to structure my project to make the previous @font-face definition work?

First, and as mentioned before, you need to use the actual names of your font files and define every possible format you have for that font (you will likely have something like .ttf, .otf, .svg, and/or .woff, but there are others, too).

And, second, and key to this configuration πŸ‘‰ You need to create a fonts directory inside the /public directory.

This is necessary because Next.js serves static files under the /public folder, so since fonts are a static resource, they have to be located in there.

Note that every resource placed in the /public directory should be routed using a slash / before its name.

Making our global theme accessible by the app

As a final step to be able to start using our custom icon font, we just need to import the IconFont component into our main app component, _app.js, like this:

_app.jsx

import IconFont from '../components/IconFont';
const MyApp = ({ Component, pageProps }) => {
  return (
    <>
      <IconFont />
      <Component {...pageProps} />
    </>)
}

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

If everything goes as expected, this will be the result of our styled button, to which we have appended a heart icon:

Alt Text

Otherwise, if something went wrong along the way, this is what we'll see:

Alt Text

Getting a square instead of the actual icon can mean:

  • The icon font has been found but the value for the content you have provided is not part of the list of values of that font.
  • There's a problem with the location of the fonts: the specified font files are not located at the route you have provided.
  • Something wasn't configured properly.

Older versions of Next.js

As of Next.js 11, no extra configuration for Webpack is required to translate font file formats. If you're using an older version, it's highly recommended that you update your package version by running the following command:

npm install next@latest
Enter fullscreen mode Exit fullscreen mode

In case you need to use an outdated version for whatever reasons, keep in mind that a little bit of further configuration will be required: you'll need to install the file-loader Webpack loader, which will handle font formats appropriately and bundle them up to include them in the final bundle that will be served to the browser, and then, you'll have to add the corresponding configuration in next.config.js.


And this is all for the third Styled Components 101 lecture!

Stay tuned to know more about Styled Component in future episodes of the series.

A big thanks for reading πŸ€— and don't hesitate to reach out to me if you any questions or doubts about today's lecture.

Rachel Green from Friends TV Show behind a desk saying "Ask me anything"

I hope you found this article useful and I see you all in the next πŸ‘‹

πŸŽ‰ Don't forget to follow @underscorecode on Instagram and Twitter for more daily webdev content πŸ–₯πŸ–€


And last but not least... A quick friendly reminder before we go 😊

We all know there are million ways to get things done when it comes to programming and development, and we're here to help and learn, so, if you know another possible way to do what others are sharing (not better, not worse, just different), feel free to share it if you feel like it, but, please, always be kind and respectful with the author and the rest of the community. Thank you and happy coding!

Top comments (5)

Collapse
 
larsejaas profile image
Lars Ejaas

Thanks for taking time to explain all of this. Setting up Styled Components in Next.js is a hassle! Getting everything up and running with TypeScript and autocomplete on theme variables is even more work. Outch.

Collapse
 
underscorecode profile image
_CODE

Thank you for reading and for your feedback! It’s true that sometimes one has to rack their brains as to configs and it ends up becoming a real headache πŸ˜‘

Collapse
 
merichard123 profile image
Richard

This is fantastic! Thank you!! You've earned yourself a new Instagram follower!!

Collapse
 
underscorecode profile image
_CODE

Thanks a lot! πŸ˜‡

Collapse
 
shrroy profile image
Shrroy

Can we have the souce code?