こんにちは、エンジニアのみーや(@miiya387)です。
今回はReactのフレームワークである「Next.js」を公式チュートリアルをもとに触ってみました。
普段の開発ではReactを使用しているのですが、最近フロントエンド関連の記事でNext.jsをよく目にするようになったので、基本的な機能をチュートリアルを通して触ってみて知ったことをまとめたいと思います。

Next.jsとは?

Next.jsはReactベースのフレームワークです。
公式の導入部分にて、Reactを使ってWebアプリケーションを開発する際の考慮点を取り上げた上で、Next.jsはそれらをすべて解決してくれると紹介されていました。
主な特徴は以下の8つみたいです。詳細はこちら▶︎▶︎

・An intuitive page-based routing system (with support for dynamic routes)
・Pre-rendering, both static generation (SSG) and server-side rendering (SSR) are supported on a per-page basis
・Automatic code splitting for faster page loads
・Client-side routing with optimized prefetching
・Built-in CSS and Sass support, and support for any CSS-in-JS library
・Development environment with Fast Refresh support
・API routes to build API endpoints with Serverless Functions
・Fully extendable

特に2つ目のSSG, SSRの特徴が気になっていたのでチュートリアルの中で触れられると嬉しいです。
チュートリアルではシンプルなブログアプリの作成を通してNext.jsの基本を学んでいくスタイルです。

  1. Create a Next.js App
  2. Navigate Between Pages
  3. Assets, Metadata, and CSS
  4. pre-rendering and Data Fetching
  5. Dynamic Routes
  6. API Routes
  7. Deploying Your Next.js App

本記事では1~3をまずはやってみようと思います。

やってみる

1. Create a Next.js App

Setup

最初のレッスンではNex.jsのアプリケーション作成とページの表示、編集までを行います。

前提

  • Node.jsがインストールされていること(バージョンは10.13以上)
  • 任意のエディタとターミナルを用意してあること

私は今回 Node.js は v14.15.5 の環境で作業します。

Create a Next.js app

任意のディレクトリ内で以下コマンドでNext.jsアプリケーションを作成します。

$ npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"

Run the development server

作成が完了したら、 nextjs-blog に移動してサーバーを起動します

$ cd nextjs-blog
$ npm run dev

> @ dev /Users/XXXXXX/Projects/tutorial_next_js/nextjs-blog
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 1880 ms (110 modules)
Attention: Next.js now collects completely anonymous telemetry regarding usage.
This information is used to shape Next.js' roadmap and prioritize features.
You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
https://nextjs.org/telemetry

Welcome to Next.js

起動後に http://localhost:3000 にアクセスすると、以下の画面の表示がされました!
サクッとここまでこれるのは簡単で嬉しいですね!

Editing the Page

サーバーを起動したままページの編集をしていきます。
pages/index.js の Welcome を Learn に書き換えてみましょう。

import Head from 'next/head'

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1 className="title">
          Lean to <a href="https://nextjs.org">Next.js!</a>
        </h1>
・・・

なんと編集するだけでページの更新なしに編集内容がページに反映されました!

すごーい!これが Fast Refresh という特徴みたいです。
単体のエクスポートファイルの更新なら編集のあったファイルのみ、importされているファイルならimportしているファイルもセットでコンパイルして再描画してくれるみたいです。

最近自分の開発環境のjsのトランスパイルが遅くて少々ストレス・・と思っていたのでこれは嬉しい特徴ですね!
ほとんどの場合1秒以内に反映されるようです。開発が捗りますね!

次のレッスンではページの追加と、ページ間遷移を行います。

Pages in Next.js

pagesディレクトリ内に新しく posts ディレクトリを作成し、
postsディレクトリ内に以下の first-post.js ファイルを新規追加します。

export default function FirstPost() {
  return <h1>First Post</h1>
}

http://localhost:3000/posts/first-post にアクセスすると以下の画面が表示され、新しいページが作成できたことが確認できます。

これがページベースのルーティングです。pagesディレクトリの下にファイルを配置するだけで、ページファイルのパスが、アプリケーション上のページのURLのパスに対応しているため、直感的な実装が可能となっています。
ひえー!すごーい!

Link Component

続いて、今作成したページへのリンクをindex.jsに追加していきます。
ここでは Link コンポーネントを使うことでclient-side navigationによるページ間の遷移を実装してみます。

pages/index.jsに Link コンポーネントのimportを追加し、h1タグのaタグをlinkタグに置き換えます。

import Head from 'next/head'
import Link from 'next/link'

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main>
        <h1 className="title">
          Read{' '}
          <Link href="/posts/first-post">
            <a>this page!</a>
          </Link>
        </h1>

次に、pages/posts/first-post.js を次のように変更し、 トップページに戻るリンクを新しく設置します。

import Link from 'next/link'

export default function FirstPost() {
    return (
        <>
            <h1>First Post</h1>
            <h2>
                <Link href="/">
                    <a>Back to home</a>
                </Link>
            </h2>
        </>
    )
}

ページを確認すると、追加したLinkを使ってページ間の遷移ができるようになりました!

Client-Side Navigation

Linkコンポーネントによるページ間の client-side navigation は、クライアント側だけでページ表示とURLを変更するため、通常よりも高速なページ遷移が可能となります。

クライアント側だけで遷移が行われていることを確認するために、htmlのcssプロパティを変更してページ遷移をしてみましょう。

ページ遷移しても、背景色の黄色が継続して反映されていることが確認できました。
これはブラウザがページをロードせずにクライアント側のみでページ遷移できているということになります。

Code splitting and prefetching

Next.jsはコード分割を自動的に行うため、各ページに必要なものだけをロードするようになっています。
つまり、ページロード時には他のページのソースはロードしないため、複数ページ存在する場合でも高速な読み込みが可能となります。

さらに、本番ビルド時にはコンポーネントがブラウザのビューポートに表示される度に Linkコンポーネントでリンクされたページをバックグラウンドで自動的に prefetch しているため、リンクをクリックするまでにリンク先にコードが読み込まれている状態になり、高速なページ遷移ができるようになります。

ひょえ〜〜〜!

3. Assets, Metadata, and CSS

次のレッスンではページにCSSスタイルを追加します。

Assets

Download Your Profile Picture

まずは、ブログトップに表示する自身のプロフィール画像をダウンロードします。

publicディレクトリ内に images ディレクトリを新規作成し、ダウンロードした profile.jpg を格納します。

Unoptimized Image

通常のHTMLでは以下のように画像を追加します

<img src="/images/profile.jpg" alt="Your Name" data-mce-src="/images/profile.jpg">

しかし、この場合、画像の最適化や遅延読み込みなどは手動で設定する必要があります。

Image Component and Image Optimization

Next.jsの場合、imgタグを拡張子た Image コンポーネントが用意されており、画像の最適化や遅延読み込みなどを自動で行うことができます。webpにも対応しているとか。

Using the Image Component

Next.jsは、ビルド時にイメージを最適化する代わりに、ユーザーの要求に応じてオンデマンドで画像を最適化します。
画像はデフォルトで遅延読み込みされるため、ブラウザのビューポート外の画像の影響でページ速度が低下することもなく、常にレイアウトシフトを回避するように描画されます。
詳しくはこちら▶︎▶︎

便利すぎて怖い・・・・・・・

import Image from 'next/image'

const YourComponent = () => (
  <Image
    src="/images/profile.jpg" // Route of the image file
    height={144} // Desired size with correct aspect ratio
    width={144} // Desired size with correct aspect ratio
    alt="Your Name"
  />
)

このコードは後ほど使うらしいのでまだファイル追加せず先に進みます。

Metadata

Head コンポーネントを使ってページのメタデータを追加します。

Adding Head to first-post.js

import Link from 'next/link'
import Head from 'next/head'

export default function FirstPost() {
    return (
        <>
            <Head>
                <title>First Post</title>
            </Head>
            <h1>First Post</h1>
            <h2>
                <Link href="/">
                    <a>Back to home</a>
                </Link>
            </h2>
        </>
    )
}

http://localhost:3000/posts/first-post にアクセスすると指定した通りページタイトルが変更されていることが確認できました。

Third-Paty JavaScript

通常のHTMLでは、サードパーティスクリプトの追加は次のようにscriptタグを使用して行います。

<Head>
  <title>First Post</title>
  <script src="https://connect.facebook.net/en_US/sdk.js" />
</Head>

この方法でスクリプトを含めると、同じページでフェッチされた他のJavaScriptコードに対していつロードされるかが明確にわかりませんし、読み込みによりページのレンダリングをブロックしていて、ページコンテンツの読み込みが遅れる可能性があります。

Using the Script Component

Next.jsでは Script コンポーネントを使うことによって、スクリプトの読み込みや実行を最適化することができます。詳しくはこちら▶︎▶︎

import Link from 'next/link'
import Head from 'next/head'
import Script from 'next/script'

export default function FirstPost() {
    return (
        <>
            <Head>
                <title>First Post</title>
            </Head>
            <Script
                src="https://connect.facebook.net/en_US/sdk.js"
                strategy="lazyOnload"
                onLoad={() =>
                    console.log(`script loaded correctly, window.FB has been populated`)
                }
            />
            <h1>First Post</h1>
            <h2>
                <Link href="/">
                    <a>Back to home</a>
                </Link>
            </h2>
        </>
    )
}

strategyでlazyOnloadを指定しています。これにより、ページ表示速度を向上できます。
onLoadでは読み込み完了後の処理を指定できます。今回は読み込みが完了したことをコンソール出力します。

http://localhost:3000/posts/first-post にアクセスするとコンソール出力ができていることが確認できました。
また、Networkで読み込み順を確認したところ、first-post.js の後に sdk.js が読み込まれていることも確認できました。

CSS Styling

pages/index.js にはすでにいくつかのスタイル指定があります。

styled-jsxというCSS-in-JSライブラリを使用しており、Reactコンポーネント内でCSSを記述できるようになります。
Next.jsにはstyled-jsxのサポートが組み込まれていて、読み込むファイルの拡張子を .css から .sass に変更すればSassの読み込みも可能になります。

Layout Component

ここでは、すべてのページで共通で使用するレイアウトを作成していきます。

プロジェクトトップに components ディレクトリを作成し、 layout.js を追加します。

export default function Layout({ children }) {
  return <div>{children}</div>
}

次に first-post.js で作成した layout.js をimportし、一番外側に追加します。

import Link from 'next/link'
import Head from 'next/head'
import Script from 'next/script'
import Layout from '../../components/layout';

export default function FirstPost() {
    return (
        <Layout>
            <Head>
                <title>First Post</title>
            </Head>
            <Script
                src="https://connect.facebook.net/en_US/sdk.js"
                strategy="lazyOnload"
                onLoad={() =>
                    console.log(`script loaded correctly, window.FB has been populated`)
                }
            />
            <h1>First Post</h1>
            <h2>
                <Link href="/">
                    <a>Back to home</a>
                </Link>
            </h2>
        </Layout>
    )
}

Adding CSS

実際に Layout コンポーネントにスタイルを追加してみます。
components/layout.module.css を作成し、Layoutコンポーネントに追加します。

.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

http://localhost:3000/posts/first-post にアクセスすると、スタイルが適用されたことがわかります。

クラス名が layout_container__fbLkO になっているのは、cssモジュールの機能で一意のクラス名を生成しているからです。
これでクラス名の衝突を気にする必要がなくなります。

Global Styles

ここでは、グローバルに適用できるCSSスタイルを作成します。
cssモジュールはページ単位のスタイル指定に役立ちますが、すべてのページにスタイルを反映させたい場合は、以下のように指定します。
まずはすべてのコンポーネントに共通する最上位のコンポーネントを pages/_app.js として新規作成します。

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

Restart the Development Server

pages/_app.js を追加する場合は、開発サーバーを再起動する必要があります。

// Ctrl+c で起動していたサーバーを一度停止します
$ npm run dev

Adding Global CSS

プロジェクトトップにstylesディレクトリを作成し、global.cssファイルを追加します。

html,
body {
  padding: 0;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
    Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
  line-height: 1.6;
  font-size: 18px;
}

* {
  box-sizing: border-box;
}

a {
  color: #0070f3;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

img {
  max-width: 100%;
  display: block;
}

http://localhost:3000/posts/first-post にアクセスすると、スタイルが適用されていることがわかります。
global.css は pgaes/_app.js 以外のファイルでのimportはできないので注意しましょう。(公式

Polishing Layout

ここまで作成してきたレイアウトファイルを改善していきます。

Update components/layout.modle.css

layout.module.cssを次のように変更します。

.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.backToHome {
  margin: 3rem 0 0;
}

Create styles/utils.module.css

styles/utils.module.cssを次のように作成します。

.heading2Xl {
  font-size: 2.5rem;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingXl {
  font-size: 2rem;
  line-height: 1.3;
  font-weight: 800;
  letter-spacing: -0.05rem;
  margin: 1rem 0;
}

.headingLg {
  font-size: 1.5rem;
  line-height: 1.4;
  margin: 1rem 0;
}

.headingMd {
  font-size: 1.2rem;
  line-height: 1.5;
}

.borderCircle {
  border-radius: 9999px;
}

.colorInherit {
  color: inherit;
}

.padding1px {
  padding-top: 1px;
}

.list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.listItem {
  margin: 0 0 1.25rem;
}

.lightText {
  color: #666;
}

Update components/layout.js

layout.jsを次のように変更します。

import Head from 'next/head'
import Image from 'next/image'
import styles from './layout.module.css'
import utilStyles from '../styles/utils.module.css'
import Link from 'next/link'

const name = 'mi-ya'
export const siteTitle = 'Next.js Sample Website'

export default function Layout({ children, home }) {
  return (
    <div className={styles.container}>
      <Head>
        <link rel="icon" href="/favicon.ico" />
        <meta
          name="description"
          content="Learn how to build a personal website using Next.js"
        />
        <meta
          property="og:image"
          content={`https://og-image.vercel.app/${encodeURI(siteTitle)}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
        />
        <meta name="og:title" content={siteTitle} />
        <meta name="twitter:card" content="summary_large_image" />
      </Head>
      <header className={styles.header}>
        {home ? (
          <>
            <Image
              priority
              src="/images/profile.jpg"
              className={utilStyles.borderCircle}
              height={144}
              width={144}
              alt={name}
            />
            <h1 className={utilStyles.heading2Xl}>{name}</h1>
          </>
        ) : (
          <>
            <Link href="/">
              <a>
                <Image
                  priority
                  src="/images/profile.jpg"
                  className={utilStyles.borderCircle}
                  height={108}
                  width={108}
                  alt={name}
                />
              </a>
            </Link>
            <h2 className={utilStyles.headingLg}>
              <Link href="/">
                <a className={utilStyles.colorInherit}>{name}</a>
              </Link>
            </h2>
          </>
        )}
      </header>
      <main>{children}</main>
      {!home && (
        <div className={styles.backToHome}>
          <Link href="/">
            <a>← Back to home</a>
          </Link>
        </div>
      )}
    </div>
  )
}

Update pages/index.js

index.jsを次のように変更します。

import Head from 'next/head'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'

export default function Home() {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>
        <p>
          (This is a sample website - you’ll be building a site like this on{' '}
          <a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
        </p>
      </section>
    </Layout>
  )
}

http://localhost:3000/http://localhost:3000/posts/first-post にアクセスすると、それぞれスタイルが変更されたことが確認できました。

まとめ

この記事ではNext.jsのチュートリアルを半分やってみるところまでを記載しました。
ずっとReactを使ってきたので記述方法など同じで理解しやすかったですし、Next.jsは標準サポートしている機能が多くとても便利という印象です。
もう少し触ってみてからReactとの違いをまとめておけると今後のためのインプットになりそうな予感がします。
ここまでのソースはgithubに上げたので興味のある方はぜひ一緒にNext.jsと仲良くなっていきましょう!

また後日残りの半分もやってみて記事にしたいと思います。

Join Us !

ウエディングパークでは、一緒に働く仲間を募集しています!
ご興味ある方は、お気軽にお問合せください(カジュアル面談から可)

採用情報を見る