Cut Into The Jamstack book logo

Cut Into TheJamstack

Next.js Fundamentals Part 2

Mike Cavaliere

01/14/2022

In my previous article I covered a wide array of Next.js basics with examples. Let's tie up that batch of basics with some remaining core essentials.

Next.js's routing system is great for a number of reasons—not only is it easy to use, but it makes it easy to keep your site's navigation fast. Using the Next.js <Link> component is how you make this happen.

Under the hood, <Link> uses prefetching to load the page you're linking to before you actually navigate to it, which makes it load faster when you do click the link. In addition it knows how to redirect the page and change the URL in the browser without loading more than it needs to.

The result is super-snappy links. Click around this site and you'll see this in effect.

Usage is simple. All you have to remember is to default to using <Link> and forget about using a standard <a> tags.

//
// pages/page1.js
//
import Link from 'next/link';

export const Page1 = () => (
  <>
    <Link href="/page2">to Page 2</a></Link>
  </>
);

//
// pages/page2.js
//
import Link from 'next/link';

export const Page2 = () => (
  <>
    <Link href="/page1">to Page 1</a></Link>
  </>
);


The <Link> component has a bunch of useful options, but the most important think about the component is that you use it exclusively for navigation. Don't put an <a href="..."> inside a Next.js site or you'll be sacrificing speed.

The Next.js Image component

Like the <Link> component, the Next.js <Image> component packs a lot of magic under the hood that makes life easier.

Images are one of the big culprits of slow page loads. The <Image> component automatically handles some key things we used to do manually to speed up pages with images on them:

  • Lazy loading images to prevent them from blocking the page load
  • Using srcset to specify different image resolutions for different browser breakpoints
  • Caching the image to make subsequent loads fast

So obviously, use the <Image> tag whenever you need to load an image, barring any specific exceptions.

Usage is pretty easy. A few things to keep in mind:

  • Most of the time you need to specify width and height. The exception is when the image is static (i.e., it's saved inside the same repo as your app) and when layout="fill".
  • You need to specify the layout attribute, which tells the image how to stretch relative to its container. This trips me up often, so in addition to the official docs, here's a Stackblitz that I find helpful for seeing the different layouts in action.

Styling things

By default, Next.js lets you write CSS write inside your JavaScript, and reference it in your React components.

Importing and using CSS files

CSS files can be imported in Next.js globally for the whole app, using a custom <App> component (more on these later in this article) like so:

/*
 * styles/global.css
 */
body {
  background: #f4ebd9;
  color: #0c0910;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 16px;
}

//
// pages/_app.js
//
import '../styles/global.css'


export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

For pages and components, you can use CSS modules; they're files with the .module.css extension that have all of their styles in classes. Next.js can import these as JavaScript objects that you can directly pass to the className attribute.

This allows for modular css without having to install any dependencies, which is convenient for getting a project started quickly.

/*
 * styles/MyPage.module.css
 */

.left {
  background: #a393bf;
  float: left;
  height: 300px;
  width: 50%;
}

.right {
  background: #a4303f;
  float: right;
  height: 300px;
  width: 50%;
}

import styles from "../styles/MyPage.module.css";

export default function MyPage() {
  return (
    <>
      <div className={styles.left}>Left div</div>
      <div className={styles.right}>Right div</div>
    </>
  );
}

Other ways to style a Next.js app

You don't have to depend on plain ol' CSS to style your Next.js apps if you don't want to, naturally.

Next.js supports Sass, for example, using either .scss or .sass syntax.

You can also use popular UI libraries like TailwindCSS or Chakra-UI, the latter of which is my current favorite and I use in the book.

Global style and behavior with a custom App component

When you need to add components (or context, or styles, etc) that affect all pages in the site, you can define a custom Next.js <App> component. Create pages/_app.js (or pages/_app.tsx for TypeScript projects) with a default export like so:

//
// pages/_app.js
//
export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

The boilerplate code just takes a component and props, and renders them out. Pretty straightforward.

If you want to add a UI library or global stylesheet, this is the place to do it. For example, setting ui Chakra-UI in Next.js looks like this:

import { ChakraProvider } from '@chakra-ui/react'

export default function MyApp({ Component, pageProps }) {
  return (
    <ChakraProvider>
      <Component {...pageProps} />
    </ChakraProvider>
  )
}


The global _app.js is a great place for any global React Context API providers you want in the app. Setups like the below are fairly common:

import { UILibraryProvider } from '@some/ui-library'
import { AuthProvider } from '@some/authentication-library'
import { StateProvider } from '@some/state-library'

export default function MyApp({ Component, pageProps }) {
  return (
    <UILibraryProvider>
      <StateProvider>
        <AuthProvider>
          <Component {...pageProps} />
        </AuthProvider>
      </StateProvider>
    </UILibraryProvider>
  )
}

You can also use react hooks like useEffect in _app.js if you want to run them on every page. Do this sparingly, naturally, since you don't want to run lots of side effects everywhere if you don't need to. Here's an example of how we connect Google Analytics to Next.js using useEffect inside _app.js (extracted from the Next.js examples repo):

import { ReactElement, ReactNode, useEffect } from 'react';
import { useRouter } from 'next/router';
// This is a wrapper around some basic Google Analytics functions.
import * as analytics from 'lib/analytics';

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

  useEffect(() => {
    // The handler functions registers a single pageview with Google Analytics.
    const handleRouteChange = (url: URL) => {
      analytics.pageview(url);
    };
    // When the page mounts, add a listener for route changes.
    router.events.on('routeChangeComplete', handleRouteChange);

    // When the page unmounts, remove the listener.
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
    };
    // Run this every time router.events changes.
  }, [router.events]);

  return (
    <Component {...pageProps} />
  )
}



The above should fill in some of the gaps from Part 1 of this series on Next.js basics. At this point you should be able to create pages and routes in Next.js, style them, render images, load and render static content, and keep load times and navigation very fast.

Supported by

eb-logo-gradient-black-textCreated with Sketch.

© 2022 Mike Cavaliere. All rights reserved

TwitterMediumLinkedIn