Chapter 1: Next.js

Strongly Typed Next.js

link Next.js

By the end of this lesson, developers will be able to:

  • Create a Next.js web application with TypeScript
  • Include custom fonts and styles in your web app
  • Link pages using Next.js routing

Introduction

Next.js offers some serious performance improvements over the standard React web application. Without going into too much detail, we will be using Next.js for:

If you are new to Next.js, be sure to visit their website for an overview.

link Installation

Let's begin building the frontend Next.js app.

mkdir stream-me
cd stream-me
mkdir app
cd app
npm init -y
npm install next react react-dom

Open package.json and add the following scripts:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}

We can specify rules on how TS should be compiled into JS. These rules are listed in a tsconfig.json file. Create a new file called app/tsconfig.json, then run npm run dev or yarn dev and Next.js will guide you through the installation of the required packages to finish the setup:

npm install --save-dev typescript
npm install --save-dev @types/react @types/react-dom @types/node

After installing TypeScript, run npm run dev. Next.js should automatically generate a tsconfig.json.

TypeScript Config

Let's make one modification to the newly generated file app/tsconfig.json:

{
  "compilerOptions": {
    // ...
    "baseUrl": "."
  }
}

With this change, Next.js allows us to reference files from the root directory of our project. This will remove the need for relative file imports.

Instead of:

import { Component } from '../../components/example';

We can simply write:

import { Component } from 'components/example';

Git Ignore

Before moving forward, let's ignore some files to prevent checking them into our source control repository.

At the root of your project insert the following inside a new .gitignore file.

.env
node_modules
.DS_Store
.next

link Pages

In this section, we create our first Next.js pages. Next.js serves pages from a unique folder called pages. Instead of routing with React Router, we use this folder to create routes on our website.

Let's start with three core components of Next.js:

  1. index.tsx: homepage for our website.
  2. _app.tsx: handles global page props.
  3. _document.tsx: handles global styles.

Index

Create a new directory and file, app/pages and app/pages/index.tsx and insert the following:

export default () => (
  <div>
      <p>Hello World!</p>
  </div>
);

Fire up the server with npm run dev, and you will see "Hello World" at http://localhost:3000

App Component

Let's discuss a core component of Next.js: App.

Next.js uses the App component to initialize pages. You can override it and control the page initialization. Which allows you to do amazing things like:

  • Persisting layout between page changes
  • Keeping state when navigating pages
  • Custom error handling using componentDidCatch
  • Inject additional data into pages
  • Add global CSS

To override the default App, create the file app/pages/_app.tsx as shown below:

import App from 'next/app';
import React from 'react';

class MyApp extends App {
  public render() {
    const { Component, pageProps } = this.props;

    return <Component {...pageProps} />;
  }
}

export default MyApp;

If your app is running and you just added a custom App, you'll need to restart the development server. Only required if pages/_app.tsx didn't exist before.

Document Component

A custom Document is commonly used to augment your application's <html> and <body> tags. This is necessary because Next.js injects some stylesheets into the DOM using the custom Document.

To override the default Document, create the file app/pages/_document.tsx and extend the Document class as shown below:

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

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;

<Html>, <Head />, <Main /> and <NextScript /> are required for the page to be properly rendered.

Document is only rendered in the server, event handlers like onClick won't work.

Custom Font

Let's try adding a custom font. Insert the following <link /> inside your custom Document's render method:

  render() {
    return (
      <Html>
        <Head>
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400:latin"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }

Refresh your browser at http://localhost:3000 and you will see "Hello World" with a new Roboto font.

link Material UI

Getting started with Next.js, it is important to decide which components we would like to build with. Thankfully, there are many component libraries to choose from. In this course, we will implement components with Material UI.

Server Rendering

Material-UI was designed from the ground-up with the constraint of rendering on the server, but it's up to you to make sure it's correctly integrated. It's important to provide the page with the required CSS, otherwise the page will render with just the HTML then wait for the CSS to be injected by the client, causing it to flicker. To inject the style down to the client, we need to:

  • Create a fresh, new ServerStyleSheets instance on every request.
  • Render the React tree with the server-side collector.
  • Pull the CSS out.
  • Pass the CSS along to the client.
  • On the client side, the CSS will be injected a second time before removing the server-side injected CSS.

Installation

Let's begin with the installation of Material UI:

npm install @material-ui/core

link App Theme

Let's create a Material UI theme for our website.

Create a new directory and file, app/lib and app/lib/theme.ts and insert the following:

import grey from "@material-ui/core/colors/grey";
import { createMuiTheme } from "@material-ui/core/styles";

const themeDark = createMuiTheme({
  palette: {
    primary: { main: grey[200] },
    secondary: { main: grey[400] },
    type: "dark",
  },
});

const themeLight = createMuiTheme({
  palette: {
    primary: { main: grey[800] },
    secondary: { main: grey[900] },
    type: "light",
  },
});

export { themeDark, themeLight };

We just created two themes: themeDark and themeLight, which are used to toggle dark and light color palettes.

Let's integrate a ThemeProvider with our global styles. Insert the following into pages/_app.tsx:

import CssBaseline from "@material-ui/core/CssBaseline";
import { ThemeProvider } from "@material-ui/core/styles";

import { themeDark, themeLight } from "lib/theme";

export default function MyApp(props) {
  const { Component, pageProps } = props;

  return (
    <ThemeProvider theme={false ? themeDark : themeLight}>
      <CssBaseline />
      <Component {...pageProps} />
    </ThemeProvider>
  );
}

When rendering, we will wrap Component, the root component, inside a ThemeProvider to make the style configuration and the theme available to all components in the component tree.

import { useEffect } from "react";

export default function MyApp(props) {
  const { Component, pageProps } = props;

  useEffect(() => {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector("#jss-server-side");
    if (jssStyles && jssStyles.parentNode) {
      jssStyles.parentNode.removeChild(jssStyles);
    }
  }, []);

  {/* ... */}
}

To prevent loading server-side injected CSS, we also added the useEffect block.

link Server Stylesheets

The final step on the server-side is to inject the initial component HTML and CSS into a template to be rendered on the client side.

Let's create ServerStyleSheets for each request in pages/_document.tsx:

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

class MyDocument extends Document {

  static async getInitialProps(ctx) {
    // Render app and page and get the context of the page with collected side effects.
    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 fragment is rendered after the app and page rendering finish.
      styles: [
        ...React.Children.toArray(initialProps.styles),
        sheets.getStyleElement(),
      ],
    };
  }

  {/* ... */}
}

With the setup finally completed, we can start building Material UI components inside our application.

link Linking Pages

Let's start modifying pages/index.tsx with some Material UI components:

import React from "react";
import { Container, Typography, Box, Button } from "@material-ui/core";
import Link from "next/link";

export default function Index() {
  return (
    <Container maxWidth="sm">
      <Box my={4}>
        <Typography variant="h4" component="h1" gutterBottom>
          Next.js example
        </Typography>
        <Link href="/about">
          <Button variant="contained" color="primary">
            Go to the about page
          </Button>
        </Link>
      </Box>
    </Container>
  );
}

About Page

Copy and paste the contents of pages/index.tsx into a new file, pages/about.tsx:

import React from "react";
import { Container, Typography, Box, Button } from "@material-ui/core";
import Link from "next/link";

export default function About() {
  return (
    <Container maxWidth="sm">
      <Box my={4}>
        <Typography variant="h4" component="h1" gutterBottom>
          Next.js example
        </Typography>
        <Link href="/">
          <Button variant="contained" color="primary">
            Go to the index page
          </Button>
        </Link>
      </Box>
    </Container>
  );
}

When you refresh the page at http://localhost:3000/, you may notice that the styles on each page are loading without delay.

Congratulations!

Today we created a Next.js application, with Material UI as our theme provider. In the next lesson, we will begin to design the backend models.

link References

format_list_bulleted
help_outline