公式チュートリアルでNext.jsに入門してみた (1) 〜アプリ新規作成、ページ遷移、スタイリング編〜

2022.01.02

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、現在注目されているフロントエンドフレームワークNext.jsへの入門のために、次の公式チュートリアルを数回のシリーズに分けてこなしていき、基本的な機能に触れていこうと思います。

Next.jsとは

Next.jsは、オープンソースで提供されるReactベースのフロントエンドフレームワークです。

こちらによるとNext.jsの特徴は次のようなものがあり、プロダクション環境で必要とされるあらゆる機能と、最高の開発者エクスペリエンスを提供できるように設計されています。

  • 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

以下は意訳したもの。

  • 直感的なページベースのルーティングシステム(dynamic routesのサポート)
  • static generation(SSG)server-side rendering(SSR)の両方のpre-rendering方式をページごとにサポート
  • ページのロード高速化のための自動コード分割
  • プリフェッチを最適化したclient-side routing
  • CSSとSassのビルトインサポートおよびCSS-in-JSライブラリのサポート
  • Development環境でのFast Refreshのサポート
  • Serverless Functionsを使用したAPIエンドポイントを構築するためのAPI routes
  • 完全な拡張可能性

チュートリアルを進めていく中でこれら特徴について触れていければと思います。

やってみた

それではチュートリアルに沿ってNext.jsアプリを作成してみます。参考にしたのは次の公式チュートリアルです。

本記事(1)では、チュートリアルのうち「Create a Next.js App」、「Navigate Between Pages」および「Assets, Metadata, and CSS」をやっていき、Next.jsのアプリ新規作成ページ遷移およびスタイリングについて理解を深めていきます。

Create a Next.js App

ここではアプリケーションの新規作成を行います。

Setup

Create a Next.js app

create-next-appコマンドを使用してNext.jsアプリケーションを新規作成します。

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

--exampleオプションではテンプレートとするソースコードを指定できます。

--use-npmオプションを指定するとnpmを使用してブートストラップすることを明示的に指定できます。npm以外だとyarnが使用可能です。

Run the development server

次のコマンドを実行して、アプリケーションの開発サーバーを起動します。

$ cd nextjs-blog
$ npm run dev

> @ dev /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/nextjs-tutorial/nextjs-blog
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 1618 ms (158 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

wait  - compiling / (client and server)...
event - compiled client and server successfully in 265 ms (174 modules)

起動後にhttp://localhost:3000にアクセスすると、アプリケーションにアクセスできました!

Editing the Page

index.jsファイルを次のように<h1>の内容をWelcome toからLearnへ変更します。

pages/index.js

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">
          Learn <a href="https://nextjs.org">Next.js!</a>
        </h1>

ファイルの編集を保存すると、ページの更新などをせずに即座に変更が反映されました!

これが開発サーバーのFast Refreshによる即時反映とのこと。

Fast Refresh is a Next.js feature that gives you instantaneous feedback on edits made to your React components. Fast Refresh is enabled by default in all Next.js applications on 9.4 or newer. With Next.js Fast Refresh enabled, most edits should be visible within a second, without losing component state.

Navigate Between Pages

ここではページ遷移を実装します。

Pages in Next.js

新しいページを作成します。

pages/posts/に次のファイルfirst-post.jsを作成します。

pages/posts/first-post.js

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

http://localhost:3000/posts/first-postにアクセスすると、新しいページにアクセスできました。

これがページベースのルーティングです。ソースコード上のページファイルのパス配置が、アプリケーション上のページのURLのパスに対応しているため、直感的な実装が可能となっていますね!

Link Component

LinkComponentを使ってclient-side navigationによるページ間の遷移を実装してみます。

pages/index.jsファイルで、Linkのインポートの記述を追記します。また<h1>の内容を次の通り変更します。

pages/index.js

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ファイルを次の通り変更し、トップページに戻るためのリンクを設置します。

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を使用したページ間の遷移ができるようになりました! nextjs-page-link

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

Assets, Metadata, and CSS

ここではアプリケーションのスタイリングを実装します。

Assets

Download Your Profile Picture

適当なプロフィール画像データをpublic/images配下にprofile.jpgとして配置します。

Unoptimized Image

標準的なHTMLでは画像の追加は次のように<img>を使用して行います。

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

しかしこの場合だとNext.jsにおいては画像サイズの最適化やレイジーロードは行われません。

Using the Image Component

Next.jsではImageを使用することにより画像サイズの最適化やレイジーロードを自動で行うことができます。

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

Adding Head to first-post.js

<Head>を使用するとページのメタデータを設定することができます。

pages/posts/first-post.jsファイルで、Headのインポートの記述を追記します。また<Head>の記述を次の通り追加します。

pages/posts/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にアクセスすると、<title>で指定した通りページのタブを変更できました。

Third-Party JavaScript

Adding Third-Party JavaScript

標準的なHTMLではサードパーティスクリプトの追加は次のように<script>を使用して行います。下記はFacebookソーシャルプラグインを追加している例です。

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

しかしこの場合だとFacebook SDKのフェッチにより他のページコンテンツの読み込みが遅れる可能性があります。

Using the Script Component

Next.jsではScriptを使用することにより、スクリプトの読み込みや実行を最適化することができます。

pages/posts/first-post.jsファイルで、Scriptのインポートの記述を追記します。また<Script>の記述を次の通り追加します。

pages/posts/first-post.js

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`)
        }
      />

strategylazyOnloadを指定するとスクリプトのレイジーロードを行うようにすることができます。これによりページ表示速度を向上できます。

http://localhost:3000/posts/first-postにアクセスすると、SDKロード時の処理が実行できていますね。

CSS Styling

pages/index.jsの中では、すでに次の記述が使われています。

<style jsx>{`
  …
`}</style>

これはstyled-jsxというCSS-in-JSライブラリを使用したもので、これによりReactコンポーネント内でCSSを記述できるようになります。Next.jsではstyled-jsをビルトインでサポートしています。

Layout Component

ここではレイアウトを変更してみます。

プロジェクトトップにcomponentsディレクトリを作成し、layout.module.cssファイルを作成します。これはCSSモジュールファイルで、ファイル名末尾は.module.cssとする必要があります。

components/layout.module.css

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

同じくcomponents配下に次のlayout.jsファイルを作成します。

import styles from './layout.module.css'

export default function Layout({ children }) {
  return <div className={styles.container}>{children}</div>
}

pages/posts/first-post.jsを次のように変更します。Layoutコンポーネントをimportして、すべてのタグを囲います。(先程のScriptの記述は削除して構いません。)

pages/posts/first-post.js

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

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

http://localhost:3000/posts/first-postにアクセスすると、<Layout>内の要素をセンターに配置できました。

Global Styles

ここでは、グローバルに適用できるCSSスタイルを作成します。

プロジェクトトップにstylesディレクトリを作成し、次の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;
}

pages配下に次の_app.jsファイルを作成します。これにより上述のCSSをグローバルに適用します。

pages/_app.js

import '../styles/global.css'

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

http://localhost:3000/posts/first-postにアクセスすると、スタイルを適用できていますね。

Polishing Layout

ここまで作成したアプリケーションのレイアウトを改善していきます。

Update components/layout.module.css

components/layout.module.cssを次の通り更新します。

components/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を作成します。

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

components/layout.jsを次の通り更新します。

components/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 = 'Your Name'
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

pages/index.jsを次の通り更新します。

pages/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/posts/first-postにアクセスすると、「Assets, Metadata, and CSS」で追加したプロフィール画像を表示できました。

おわりに

公式チュートリアルでNext.jsに入門してみた (1) 〜アプリ新規作成、ページ遷移、スタイリング編〜 でした。

次回はこちらです。

以上