DEV Community

Felix Mohr
Felix Mohr

Posted on • Updated on

Setting up a Blog with Next.js, React, Material-UI and Typescript

Alt Text

This article was first published at devx.sh

My own Next.js blog – how it all started

So finally, there it is – my own blog to write about the technologies I love and I work with everyday. And like many developers out there, I didn't want to choose a prebuilt solution for my blog. Instead, I wanted to build it on my own, so it will be possible to really understand what it is doing under the hood. Of course, I wanted to make use of technologies that I was (partially) already familiar with and that make sense for this project.

As I apply React almost daily at work, I chose this framework to be the core of my blog. And as this is a blog, and therefore it is serving static content, I chose Next.js as a solution for generating this content once on the server, instead of having it been rendered every time someone visits a page on the blog. This is much different from the usual client-side rendering taking place with apps based on React and other single-page frameworks, and provides a significant performance-boost. On a side note: Next.js does not only allow for static generation as we are going to see it applied here, it also makes server-side rendering possible, which means that the server generates the content at every request. This may be necessary for example to always access the most current data from a database, but is nothing that we have to deal with for our purposes.

To have a solid basis for the blog UI, I chose Material-UI. As it turned out, some configuration was necessary to make this framework play well together with Next.js. Integrating Typescript was very simple however, as Next.js takes care of all the necessary steps.

Setting up the Next.js blog project

You can find the complete code shown in this post at https://github.com/FelixMohr/nextjs-material-ui-blog

Next.js with Typescript

Starting a new Next.js project is as easy as running npx create-next-app. This command sets up the basic project structure to work with. Just as the create react app project that you are probably familiar with, create-next-app automatically adds some scripts to your package.json to build your project or to run it in development mode. For running it, execute yarn dev. To integrate Typescript into your project, just create an empty tsconfig.json (touch tsconfig.json). Next.js will detect this file and initialize it.

Now, add @types/node and @types/react and of course typescript to your dev-dependencies. The reason for the node types is that we are going to write server-side code for rendering, and there we will need modules such as fs and path.

You can change the file extension of all Javascript files in your project to .ts or .tsx now and immediately start working with Typescript, no further steps are necessary here.

Integrating Material-UI with Next.js

It's a bit more involved to make Next.js integrate with Material-UI.

First, install all of the necessary dependencies and one dev-dependency:

    "@material-ui/core": "^4.11.0",
    "@material-ui/icons": "^4.9.1",
    "@material-ui/styles": "^4.10.0",
...
    "@types/material-ui": "^0.21.8",
Enter fullscreen mode Exit fullscreen mode

The reason for us having to do some additional work here is that Material-UI makes heavy use of styled components. Styled components can be applied in server-side generated code, but this makes it necessary for the developer to perform some additional steps. Luckily, the Material-UI team prepared a starter project that demonstrates
well what has to be done. Basically, you need to follow these steps:

1. create a custom file /pages/_document.tsx and add fill it as follows:

import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '@material-ui/core/styles';
import theme from '../src/theme';

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <meta name="theme-color" content={theme.palette.primary.main} />
          <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:300,400,500,700&display=swap" />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto+Slab:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

MyDocument.getInitialProps = async (ctx) => {
  const sheets = new ServerStyleSheets();
  const originalRenderPage = ctx.renderPage;

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
    });

  const initialProps = await Document.getInitialProps(ctx);

  return {
    ...initialProps,
    styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
  };
};
Enter fullscreen mode Exit fullscreen mode

_document.ts is a file which is processed on server-side only. What we are doing here is collecting the necessary CSS styles generated by Material-UI and injecting them into the document as a string.
This way, we avoid any flickering when the client receives the page. If we were to skip the above step, the client would initially display the page which was rendered on the server and then inject its own styles, which could cause flickering.

Feel free to add types to this document – I keep it as it is in the original repository, as we'll not have to further adapt it.

2. In your _app.tsx, apply a useEffect hook to remove the CSS that were injected on server-side from the client-side app:

  useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side')
    if (jssStyles) {
      jssStyles.parentElement.removeChild(jssStyles)
    }
  }, [])
Enter fullscreen mode Exit fullscreen mode

By doing this, we allow the client to take over styling the app as soon as its ready.

To better understand what these two steps do, we can take a look into the generated HTML page of any document. Right at the top, it contains a section as follows:

<style id="jss-server-side">.MuiSvgIcon-root {
  fill: currentColor;
  width: 1em;
  height: 1em;
  display: inline-block;
  font-size: 1.5rem;
  transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
  flex-shrink: 0;
  user-select: none;
}
.MuiSvgIcon-colorPrimary {
  color: #0f4c75;
}
.MuiSvgIcon-colorSecondary {
  color: #3282b8;
}
<!-- ... -->
</style>
Enter fullscreen mode Exit fullscreen mode

According to the state of the app on client-side, these values may need to change. This blog, for example, has a light mode. The values above will be active with the dark mode only. When activating the light mode and inspecting the active styles with the developer tools,
we can see the following entry:

.MuiSvgIcon-colorPrimary {
  color: #6886c5;
}
Enter fullscreen mode Exit fullscreen mode

That's why the server-side values are removed on the client.

In general, if you have some setup code in your components that you only want to execute on the client, this can be achieved with the useEffect hook and an empty dependency array. However, this should be done as sparingly as possible, as otherwise we lose the benefits of having a pre-rendered page with a minimum amount of Javascript code having to be executed by the clients.

By the way: if you see warnings such as the following, they will be resolved by the two steps just mentioned:

Warning: Prop 'className' did not match. Server: "MuiTypography-root-246 MuiTypography-title-252 MuiTypography-colorInherit-265 PageComponent-flex-207" Client: "MuiTypography-root-44 MuiTypography-title-50 MuiTypography-colorInherit-63 PageComponent-flex-5"
Enter fullscreen mode Exit fullscreen mode

Adding themes to the blog with Material-UI themes

As far as the basic project setup goes, we are all done now, and can start to theme our Next.js app with Material-UI themes. You may have noticed that our _document.tsx already makes use of the selected theme:

<meta name="theme-color" content={theme.palette.primary.main} />
Enter fullscreen mode Exit fullscreen mode

In the further posts of this article series, I'm going to talk about some additional meta tags that can be added dynamically using Next.js. The theme-color value tells user agents how to style the UI surrounding the displayed page and this way enhances the user experience.

Theme Color
Image credit: from Icons & Browser Colors, created and shared by Google and used according to terms described in the Creative Commons 4.0 Attribution License.

I wanted to make it possible in my blog for the user to chose between a dark and a light mode. We'll see in the next post how toggling the theme can be implemented. Therefore, I wrote both a dark and a light theme in src/theme/theme.ts:

import { createMuiTheme, ThemeOptions } from '@material-ui/core'

export const paletteColorsDark = {
  primary: '#0f4c75',
  secondary: '#3282b8',
  error: '#E44C65',
  background: '#1b262c',
  text: '#bbe1fa',
}

export const paletteColorsLight = {
  primary: '#6886c5',
  secondary: '#ffe0ac',
  error: '#E44C65',
  background: '#f9f9f9',
  text: '#050505',
}

const options = (dark: boolean): ThemeOptions => {
  const paletteColors = dark ? paletteColorsDark : paletteColorsLight
  return {
    palette: {
      type: dark ? 'dark' : 'light',
      primary: {
        main: paletteColors.primary,
      },
    // ...
    }
  }
}
export const darkTheme = createMuiTheme(options(true))
export const lightTheme = createMuiTheme(options(false))
Enter fullscreen mode Exit fullscreen mode

And that's it for the basic setup! If you would like to see some of the actual implementation details of my blog – e.g., how the articles are saved in Markdown files and transformed into website content with syntax highlighting on the server side by Next.js, stay tuned for the next articles in this series.

Top comments (7)

Collapse
 
husniadil profile image
Husni Adil Makmur

Great article!

Collapse
 
diamantisufi profile image
Diamant

Thanks for the Article. It's the only guide that made it work on my project.

Collapse
 
asdev808 profile image
asdev808

Repo is gone!

Collapse
 
rodrigozan profile image
rodrigozan

Thanks for this post! Wonderfull!

Collapse
 
samx23 profile image
Sami Kalammallah

Thank you so much, the two steps really helps me out to get rid the warning. :)

Collapse
 
claudsondouglas profile image
Claudson

wonderful article! thanks

Collapse
 
sybercoda profile image
Ryan Moody

I’m confused. You stated that the blog would be statically generated but the code is making use of getInitialProps which is for server side rendering. What am I missing?