Next.jsの公式チュートリアルをやってみた(1/3)


概要

業務でNext.jsを触る機会を得たため、まずは下記チュートリアルをやってみました。
チュートリアルの章ごとの備忘録をまとめます。
https://nextjs.org/learn/basics/getting-started

Getting Started

Introduction

Next.js良いぞ!と機能紹介しているページ。一旦読み飛ばす

Setup

作業用のhello-nextディレクトリを作成し、以降ここで作業。
まずは、npmコマンドで必要なライブラリをインストールし、
ビュー用のファイルを置くことになるpagesディレクトリを作成します。

$ mkdir hello-next
$ cd hello-next
$ npm init -y
$ npm install --save react react-dom next
$ mkdir pages
  • 作業後のディレクトリ構成
$ ll
drwxr-xr-x  524 aota  staff    16K 12  9 21:00 node_modules/
-rw-r--r--    1 aota  staff   263K 12  9 21:00 package-lock.json
-rw-r--r--    1 aota  staff   324B 12  9 21:00 package.json
drwxr-xr-x    2 aota  staff    64B 12  9 21:08 pages/
  • package.jsonの中身
$ cat package.json 
{
  "name": "hello-next",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^9.1.4",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  }
}

上記package.jsonのscriptsを下記に書き換えるよう指示されるので置換する。

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}
  • 置換後のpackage.jsonの中身
$ cat package.json 
{
  "name": "hello-next",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^9.1.4",
    "react": "^16.12.0",
    "react-dom": "^16.12.0"
  }
}

あとは、下記コマンド実行で準備完了!

$ npm run dev

ブラウザでhttp://localhost:3000にアクセスしてみる

何が表示された?と聞かれるので、404だよーと選択し次へ。

404 Page

404表示されてるかい?それでOKだ

Creating Our First Page

先程作ったpagesディレクトリ配下にindex.jsを作る

$ vi pages/index.js
  • pages/index.jsの中身
const Index = () => (
  <div>
    <p>Hello Next.js</p>
  </div>
);

export default Index;

ブラウザで先程開いていたhttp://localhost:3000をリロードしてみる

Hello Next.jsと表示されてるのでOK!

ここで、シンタックスエラーを起こしてみよう。

タグの閉じタグを消してみる

  • pages/index.jsの中身
const Index = () => (
  <div>
    <p>Hello Next.js
  </div>
);

export default Index;

ブラウザで再度http://localhost:3000を開いてみる

何が表示された?と聞かれるので、Syntaxエラーがでたよと選択し次へ

Handling Errors

Next.jsではデフォルトでブラウザにエラーを出力するよ。
エラーを直したらリロードしなくてもページが再レンダリングされるんだ。

You are Awesome

ファーストステップはこれでおしまい!

Navigate Between Pages

単体のページは、できたので今度は複数のページを作ろう。

先程作ったpagesディレクトリ配下にabout.jsを作る

$ vi pages/about.js
  • pages/about.jsの中身
export default function About() {
  return (
    <div>
      <p>This is the about page</p>
    </div>
  );
}

ブラウザでhttp://localhost:3000/aboutにアクセスしてみる

うむ、ちゃんと表示された。

次は、これらのページをつなげてみる
ただ<a>タグで書いちゃうと、サーバーとの通信が発生するよね。
クライアントサイドでナビゲーションしたいから、<Link>タグを使うよ。
使い方は次回。

Using Link

作っていたpages配下のindex.js<Link>タグを使うように書き換える。

$ vi pages/index.js
  • pages/index.jsの中身
// This is the Link API
import Link from 'next/link';

const Index = () => (
  <div>
    <Link href="/about">
      <a>About Page</a>
    </Link>
    <p>Hello Next.js</p>
  </div>
);

export default Index;

ブラウザでhttp://localhost:3000/aboutにアクセスしてみる

About Pageをクリックしてaboutページに遷移後、ブラウザの戻るボタンで戻っても
正常にindexページに遷移することが確認できる。
(サーバーにリクエストがいっていないのは、Chromeの開発者モードのNetworkタブから確認できる)

Client-Side History Support

先程、ブラウザの戻るボタンで戻れたのは、
location.history<Link>タグがハンドリングしてたからなんだ。

Adding Link Props

属性値を追加したいときあるよね。
title属性を追加したい場合は、こうするはずだ。

  • pages/index.jsの中身
// This is the Link API
import Link from 'next/link';

const Index = () => (
  <div>
    <Link href="/about">
      <a title="About Page">About Page</a>
    </Link>
    <p>Hello Next.js</p>
  </div>
);

export default Index;

これは正常にレンダリングされる。では下記の場合はどうか。

  • pages/index.jsの中身
// This is the Link API
import Link from 'next/link';

const Index = () => (
  <div>
    <Link href="/about" title="About Page">
      <a>About Page</a>
    </Link>
    <p>Hello Next.js</p>
  </div>
);

export default Index;

ブラウザでhttp://localhost:3000/aboutにアクセスしてみる
Chromeの開発者モードのElementsタブを確認してもtitle属性は、<a>タグに追加されていない。

また、Chromeの開発者モードのConsoleタブを確認するとエラーがでている。

何が表示された?と聞かれるので、title属性はないしConsoleでエラーがでたよと選択し次へ

Link is Just a Higher Order Component (HOC)

実際、title属性を追加しても<Link>タグには、なんの影響もないんだ。
なぜなら、hrefか近い属性しか<Link>タグは許容していないからだ。
<Link>タグもといnext/linkがHOCというもので、通常のコンポーネントではないということ。)

属性を追加したい場合は、その下のコンポーネント、今回であれば<a>タグに渡せば良い。
また<Link>タグの子コンポーネントとして必要なことは、onClick属性が使えることだけだ。

Link is Simple, but Powerful

これまで基本的な<Link>タグの使い方をみてきたけど、
今後のレッスンではもっと面白い使い方を紹介するよ。
それまでは、Next.js Routing documentationをみてみよう。

Using Shared Components

Introduction

共通のHeaderコンポーネントを作って、複数のページで読み込んでみよう

Create the Header Component

componentsディレクトリを作成し、配下にHeader.jsを作成する。

$ mkdir components
$ vi components/Header.js
  • components/Header.jsの中身
import Link from 'next/link';

const linkStyle = {
  marginRight: 15
};

const Header = () => (
  <div>
    <Link href="/">
      <a style={linkStyle}>Home</a>
    </Link>
    <Link href="/about">
      <a style={linkStyle}>About</a>
    </Link>
  </div>
);

export default Header;

Using the Header Component

pages/index.jsとpages/about.jsを編集し、先程作成したHeaderコンポーネントを読みこむ。

  • pages/index.jsの中身
import Header from '../components/Header';

export default function Index() {
  return (
    <div>
      <Header />
      <p>Hello Next.js</p>
    </div>
  );
}
  • pages/about.jsの中身
import Header from '../components/Header';

export default function About() {
  return (
    <div>
      <Header />
      <p>This is the about page</p>
    </div>
  );
}

ブラウザでhttp://localhost:3000にアクセスしてみる

Headerコンポーネントが読み込まれ、Home、Aboutそれぞれで遷移することが確認できる。

ここでちょっとした変更をしてみよう。
1. $ npm run devで起動したアプリケーションをSTOPする。(Ctrl+C
2. componentsディレクトリをcompsにリネームしてみる。
3. pages/index.jsおよびpages/about.jsで、Headerコンポーネントの読み込み先を../comps/Headerに変更する。
4. アプリケーションを再起動する($ npm run dev

  • pages/index.jsの中身
import Header from '../comps/Header';

export default function Index() {
  return (
    <div>
      <Header />
      <p>Hello Next.js</p>
    </div>
  );
}
  • pages/about.jsの中身
import Header from '../comps/Header';

export default function About() {
  return (
    <div>
      <Header />
      <p>This is the about page</p>
    </div>
  );
}

何が表示された?と聞かれるので、問題なく動いたと選択し次へ

The Component Directory

ちゃんと動くよね。コンポーネントを置くディレクトリは好きな名前にしていいんだ。
特別な名前が必要なのは、/pagesディレクトリと、/publicディレクトリだけなんだ。
もちろん、コンポーネントは/pagesディレクトリ配下にも作成できる。
ただ今回そうしなかったのは、Headerコンポーネントへの直接のリンクが不要だからだ。
http://localhost:3000/Headerにアクセスするのは不要。)

※ ここでcompsは、componentsにリネームして戻しておく。アプリケーションの再起動も忘れずに

The Layout Component

共通のCSSスタイルをあてたいときあるよね。
共通のレイアウトコンポーネントを作っていくよ。

まず、componentsディレクトリ配下にMyLayout.jsを作成するよ。

  • components/MyLayout.jsの中身
import Header from './Header';

const layoutStyle = {
  margin: 20,
  padding: 20,
  border: '1px solid #DDD'
};

const Layout = props => (
  <div style={layoutStyle}>
    <Header />
    {props.children}
  </div>
);

export default Layout;

そして、このレイアウトコンポーネントを使うためにpages/index.jsとpages/about.jsを編集する。

  • pages/index.jsの中身
import Layout from '../components/MyLayout';

export default function Index() {
  return (
    <Layout>
      <p>Hello Next.js</p>
    </Layout>
  );
}
  • pages/about.jsの中身
import Layout from '../components/MyLayout';

export default function About() {
  return (
    <Layout>
      <p>This is the about page</p>
    </Layout>
  );
}

ブラウザでhttp://localhost:3000/aboutにアクセスしてみる

レイアウトコンポーネントで指定したborder: '1px solid #DDD'の枠線があることが確認できた。

次に、components/MyLayout.jsから{props.children}を削除してみよう。

  • components/MyLayout.jsの中身
import Header from './Header';

const layoutStyle = {
  margin: 20,
  padding: 20,
  border: '1px solid #DDD'
};

const Layout = props => (
  <div style={layoutStyle}>
    <Header />
  </div>
);

export default Layout;

ブラウザでhttp://localhost:3000/aboutにアクセスしてみる

何が表示された?と聞かれるので、コンテンツが消えたと選択し次へ

Rendering Child Components

{props.children}を削除すると、Layoutコンポーネントの中においたコンテンツはレンダリングされないんだ。
Layoutコンポーネントの作成方法をひとつ紹介したけど、他にも作成する方法があるよ。

Method 1 - Layout as a Higher Order Component

// components/MyLayout.js

import Header from './Header';

const layoutStyle = {
  margin: 20,
  padding: 20,
  border: '1px solid #DDD'
};

const withLayout = Page => {
  return () => (
    <div style={layoutStyle}>
      <Header />
      <Page />
    </div>
  );
};

export default withLayout;
// pages/index.js

import withLayout from '../components/MyLayout';

const Page = () => <p>Hello Next.js</p>;

export default withLayout(Page);
// pages/about.js

import withLayout from '../components/MyLayout';

const Page = () => <p>This is the about page</p>;

export default withLayout(Page);

Method 2 - Page content as a prop

// components/MyLayout.js

import Header from './Header';

const layoutStyle = {
  margin: 20,
  padding: 20,
  border: '1px solid #DDD'
};

const Layout = props => (
  <div style={layoutStyle}>
    <Header />
    {props.content}
  </div>
);

export default Layout;
// pages/index.js

import Layout from '../components/MyLayout.js';

const indexPageContent = <p>Hello Next.js</p>;

export default function Index() {
  return <Layout content={indexPageContent} />;
}
// pages/about.js

import Layout from '../components/MyLayout.js';

const aboutPageContent = <p>This is the about page</p>;

export default function About() {
  return <Layout content={aboutPageContent} />;
}

Using Components

今回は、2つの方法について紹介したよ。
1. 共通のHeaderコンポーネントとして
2. Layoutコンポーネントとして

コンポーネントは、スタイル適用やページのLayoutなどに使えるんだ。
また、NPMモジュールから読み込む方法もあるよ。