Next.jsはオープンソースのReactベースのフロントエンドフレームワークです。パフォーマンス、SEOやアプリケーションの開発の効率化に関わるRouting, Server Side Rendering, Static Generation, Imageコンポーネントの機能が事前に組み込まされているのでそれらの機能をインストール直後から利用することができます。

本文書では、これまで一度もNext.jsを触ったことがないけれど興味があるまた今後使ってみたいという人を対象にNext.jsの基礎について説明を行っています。

本文書を読み終えると下記のことを理解することができます

  • Next.jsのインストール方法
  • 静的ファイルの作成方法
  • 動的ファイルの作成方法
  • ページ間のリンクの設定方法
  • 外部からのデータ取得と表示(getStaticProps, getServerSideProps)
  • styled-jsxによるCSSの適用方法
  • Tailwind CSSによるCSSの適用方法

Next.jsをインストールするためには事前に Node.jsのインストールを完了しておく必要があります。

環境:macOS Big Sur バージョン11.2.3、Next.jsのバージョンは10.2.0です。

Vercelへのデプロイ方法は下記の文章で公開しています。

Next.jsのインストール

npxコマンドを利用してnext.jsのインストールを行います。


 % npx create-next-app     
Need to install the following packages:
  create-next-app
Ok to proceed? (y) y
✔ What is your project named? … my-app

プロジェクトの名前を聞かれるので任意のプロジェクト名を入力してください。デフォルトの名前のまま進む場合はEnterを押します。コマンドを実行したディレクトリに任意の名前もしくはmy-appディレクトリが作成されます。

インストール中にエラー発生

※下記のエラーは環境に依存するエラーがだと思いますので発生しない場合はスキップしてください。

インストール処理は最後までいきその後にnpm run devコマンドを実行してもNext.jsの初期画面は表示されるのですが、インストールログに下記のエラーが表示されていることを確認。


gyp: No Xcode or CLT version detected!
gyp ERR! configure error 
gyp ERR! stack Error: `gyp` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onCpExit (/usr/local/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:351:16)
gyp ERR! stack     at ChildProcess.emit (events.js:314:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:276:12)
gyp ERR! System Darwin 19.6.0
gyp ERR! command "/usr/local/bin/node" "/usr/local/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /Users/mac/Desktop/my-app/node_modules/fsevents
gyp ERR! node -v v14.7.0
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok 

command line toolsに問題が発生しているということなのでパスを確認して削除を行います。


 % xcode-select --print-path
/Library/Developer/CommandLineTools
 % sudo rm -rf /Library/Developer/CommandLineTools

削除後commend line toolsのインストールを行うため下記のコマンドを実行しますが、”このソフトウェアは、現在ソフトウェア・アップデート・サーバから入手できないため、インストールできません。”と表示されインストールすることができませんでした。


 % xcode-select --install

Appleのサイト(https://developer.apple.com/download/)から手動でインストールすることができるということなので、アクセスを行います。アクセスにはApple IDが必要となります。

下記のCommand Line Tools for Xcode 12をダウンロードしてインストールを行います。

Appleのサイトからダウンロード
Appleのサイトからダウンロード

一度npx create-next-appコマンドで作成されるmy-appディレクトリを削除再度npx create-next-appコマンドを実行すると今回はエラーなしでインストールが完了しました。

next.js開発サーバの起動

インストール中に表示されるメッセージの中にnpmの3つのコマンドの説明があります。


Success! Created my-app at /Users/mac/Desktop/my-app
Inside that directory, you can run several commands:

  npm run dev
    Starts the development server. //開発時に実行。開発サーバを起動

  npm run build
    Builds the app for production. //本番用にビルドを行う

  npm start
    Runs the built app in production mode.// 本番用にビルドしたアプリを起動

We suggest that you begin by typing:

  cd my-app
  npm run dev

本書では開発を行うので最初に表示されているnpm run devコマンドを実行します。


 % cd my-app
 % npm run dev

> my-app@0.1.0 dev /Users/mac/Desktop/my-app
> next dev

ready - started server on http://localhost:3000
event - compiled successfully
event - build page: /next/dist/pages/_error
wait  - compiling...
event - compiled successfully
event - build page: /
wait  - compiling...
event - compiled successfully

localhost:3000にアクセスすると下記の画面が表示されます。これでNext.jsのインストールは完了です。

next.jsのデフォルトページ
next.jsのデフォルトページ

Hello Next.js

ディレクトリ構成

ディレクトリ構成の確認を行います。エディターはVisual Studio Codeを利用しています。ディレクトリはpages, styles, node_modules, publicの4つです。その他にREADME.mdとpackage.jsonとpackage-lock.jsonの3つのファイルが存在します。

ブラウザで表示されたページの内容はpagesディレクトリの中のindex.jsファイルに記述されています。このフォルダの中にアプリケーションのコアとなるコードを記述していくことになります。

Next.jsディレクトリ構成
Next.jsディレクトリ構成

publicディレクトリの中には、favicon.ico, vercel.svgファイルが保存されています。これらのファイルはブラウザから直接アクセスすることができます。

URLのlocalhost:3000の後ろに/vercel.svgを追加してください。publicディレクトリに保存したファイルには直接ブラウザからアクセスできることが確認できました。このことからpublicには静的なファイルを保存できることがわかります。例えばabout.htmlファイルをpublicフォルダに作成するとブラウザから直接アクセスできabout.htmlファイルの内容を表示させることも可能です。

publicフォルダのファイルへのアクセス
publicフォルダのファイルへのアクセス

stylesディレクトリの下にはCSSのファイルを保存します。CSSファイルについてはpublicディレクトリ下に作成してJavaScriptのバンドルとしてではなく直接アクセスすることでCSSを適用することも可能です。

index.jsファイルの更新

index.jsファイルを更新するとブラウザ上に表示される内容も変更されるのか確認を行います。

index.jsの中身を一度削除して下記のように更新します。


export default function Home() {
  return <h1>Hello Next.js</h1>;
}
Reactの場合はファイル上部でimport React from ‘react’を記述しますが、Next.jsでは必要ありません。

npm dev runを実行していれば、index.jsを更新するとブラウザには自動で更新内容が反映されます。

Hello Next.js
Hello Next.js

ページのソースを確認してみましょう。ブラウザで右クリックして、ページのソースを表示してください。(Chromeの場合)

ソースの確認を行う
ソースの確認を行う

ソースの中にHello Next.jsの文字列が入っていることがわかります。index.jsはJavaScriptファイルなのでJavaScriptファイルをブラウザが受け取ってJavaScriptファイルを処理してその内容を表示します。ブラウザのソースコードを見る限りブラウザが直接HTMLの情報を受け取っていることがわかります。これはNext.jsではブラウザに送信する前にpre-Renderingを行っているからです。HTMLの情報をそのまま受け取っているのでブラウザ側でJavaScriptの処理を行う必要がありません。

しかしこれだけの情報ではpre-Renderingが行われているかはわかりにくいと思うのでReactと比較して違いを見てみましょう。

同じようにReactもh1タグでHello Reactと記述します。下の画像は字が細かすぎて見えないかもしれませんが、どこにもh1タグは表示されていません。つまりブラウザは受信したJavaScriptファイルを利用してh1タグとその内容を描写していることがわかります。

Reactnのソースを見る
Reactのソースを見る

ブラウザのソースを比較することでClient RenderingとServer-side Renderingの違いも理解することができました。

別のページを作成する(静的ファイル)

Next.jsではpagesディレクトリにJavaScriptファイルを作成するだけで自動でルーティングが設定されるため簡単にページを追加することができます。

pagesディレクトリの下にabout.jsファイルを作成します。index.jsと同様に下記のコードを記述します。h1タグの中身と関数名のみ変更しています。


export default function About() {
  return <h1>About Page</h1>;
}

URL:localhost:3000/aboutページにアクセスするとAbout Pageが表示されます。Next.jsを利用するとルーティングの設定を行うことなく簡単にページが作成できることが確認できます。

About.jsページを表示
About.jsページを表示

About.jsの記述方法についてはいろいろあります。


function About() {
  return (
    <h1>About Page</h1>
  )
}

export default About

// Arrow function
const About = () => {
  return (
    <h1>About Page</h1>
  )
}

export default About

ここまでの設定では”/”(ルート)と/aboutページ以外にアクセスを行うと404エラーが表示されます。

404ページ
404ページ
404ページは存在しないページにアクセスした場合に表示されるHTTPのステータスコードです。

pagesディレクトリ内にディレクトリを作成しその下にjsファイルを作成した場合の動作も確認しておきます。

まずpagesの下にproductsディレクトリを作成し、bag.jsファイルを作成します。


export default function Bag() {
  return <h1>バックのページです</h1>;
}

想像はつくとは思いますが、localhost:3000/products/bagにアクセスするとbag.jsファイルの中身が表示されます。ページの階層化も行うことができます。

bag.jsファイルの内容を表示
bag.jsファイルの内容を表示

動的ファイルの作成(ダイナミックルーティング)

productsは商品一覧を意味し、products/bagだけではなく/products/shoesにアクセスしてもページが表示されるように設定を行なっていきます。

bag, shoesなどそれぞれに対応したファイルbag.js, shoes.jsファイルを作成するのではなくダイナミックルーティングを利用してURLのproduct/の次にどんな文字列を入れてもブラウザにファイルの内容が表示されるように設定を行います。

productsディレクトリの下にスクエアブラケットで囲まれた[name].jsファイルを作成します。


export default function Name() {
  return <h1>商品のページです</h1>;
}

/products/bagでもアクセス可能でbag.jsファイルの内容が表示されますが、bagをclothesやshoesに変更すると”商品のページです”が表示されます。

Dynamicルーティング
Dynamic Routing

URLをclothesやshoesに変更しても同じ”商品のページです”が表示されるのでURLに入れた文字列をページ内容に表示させるためuseRouterフックを利用します。useRouterフックを利用することでアクセスしてきたURLによって動的にページの内容を変更することができます。

useRouterフックはnext/routerからimportします。


import { useRouter } from "next/router";
export default function Name() {
  const router = useRouter();
  return <h1>商品{router.query.name}のページです</h1>;
}

URLに含まれる文字列はrouter.query.nameから取得することができます。

ブラウザで確認するとURLに含まれる文字列を表示することができました。

URLに含まれる文字列を表示
URLに含まれる文字列を表示

console.logを利用してrouter.queryに含まれる情報を確認します。


import { useRouter } from "next/router";
export default function Name() {
  const router = useRouter();
  console.log(router.query);
  return <h1>商品{router.query.name}のページです</h1>;
}

デベロッパーツールで確認すると下記のようにオブジェクトの中にnameが含まれていることがわかります。


{name: "shoes"}
name: "shoes"
__proto__: Object

またURLにパラメータをつけてもrouter.queryから追加したパラメータの値を取得することが可能です。

URLにはhttp://localhost:3000/products/shoes?color=redのようにcolorのパラメータと値を設定しています。

パラメータを付与
パラメータを付与

されにページの階層が深い場合にもダイナミックルーティングを利用することができます。

productsフォルダの下に[name]フォルダを作成します。さらに[name]フォルダの下に[color].jsファイルを作成します。


import { useRouter } from "next/router";
export default function Color() {
  const router = useRouter();
  console.log(router.query)
  return <h1>{router.query.name}の{router.query.color}カラーです</h1>;
}

ブラウザから/product/clothes/redにアクセスするとURLに含まれるclothesとredが表示されます。

階層の深い場合
階層の深い場合

[color].jsファイルは下記のように記述することもできます。


import { useRouter } from "next/router";
export default function Color() {
  const router = useRouter();
  const { name, color } = router.query
  return <h1>{ name }の{ color }カラーです</h1>;
}

リンクの設定

index.js, about.jsとproducts の下に[name].jsファイルを作成しましたが、リンクを設定していないのでブラウザのURLを手動で書き換える以外にページを移動する方法がありません。リンクを利用してページ移動ができるようにLinkコンポーネントの設定を行います。

index.jsが表示されるルートページからaboutページへの移動を行うためLinkを利用します。Linkはnext/linkからインポートします。Linkタグの中で移動したいページをhrefで設定します。


import Link from "next/link";
export default function Home() {
  return (
    <div>
      <ul>
        <li>
          <Link href="/about">
            <a>About</a>
          </Link>
        </li>
      </ul>
      <h1>Hello Next.js</h1>
    </div>
  );
}
About文字列にaタグがついていますが、もしclassを設定する場合はLinkタグではなくaタグにclassName属性を利用して設定を行います。

ブラウザで確認するとトップページからaboutページに移動する際にページのリロードは行われずスムーズに画面が表示されます。

Linkタグを利用したページ遷移
Linkタグを利用したページ遷移

もしLinkコンポーネントではなくこれまでのHTMLのようにaタグを利用して設定を行ってみてください。ページの移動を行うことができますがページを移動する際にページのリロードが行われページが表示されるまでに時間がかかることがわかります。

移動先の情報の入った配列を利用してダイナミックルーティングを行いたい場合も下記のように設定をすることでページのリロードなしにページ遷移することができます。


import Link from "next/link";
const products = [{ name: "bag" }, { name: "shoes" }, { name: "socks" }];
export default function Home() {
  return (
    <div>
      <ul>
        {products.map((product) => {
          return (
            <li key={product.name}>
              <Link href={`/products/${product.name}`} >
                <a>{product.name}</a>
              </Link>
            </li>
          );
        })}
        <li>
          <Link href="/about">
            <a>About</a>
          </Link>
        </li>
      </ul>
      <h1>Hello Next.js</h1>
    </div>
  );
}

リストの項目をクリックするとクリックしたページに移動します。

複数のページへのLink設定
複数のページへのLink設定

外部からデータを取得

外部から取得したデータを利用してブラウザに表示します。データの取得には、JSONPlaceholderを利用させてもらいます。

JSONPlaceholderを利用するとhttps://jsonplaceholder.typicode.com/postsにアクセスするだけで100件のPOSTS(投稿)データを取得することができます。またURLのpostsの後ろにID番号を入れることで個別のPOST(投稿)データにアクセスすることができます(posts/1, posts/2,…)。

JSONPlaceholderへはブラウザから直接アクセスしてもデータを確認することができるのでどのようなデータが取得できるのか確認したい場合はまずブラウザでアクセスを行ってみてください。

Next.jsではサーバ側でデータを取得する方法がいくつか存在し、ここでは一般的に利用される2つの方法を確認します。

  • getStaticProps(Static Generation)
  • getServerSideProps(Server-side Rendering)

getStaticPropsはStatic Generationでビルド時に一度だけデータを取得して事前にページをpre-Renderingします。getServerSidePropsはServer-side Renderingでクライアントからのアクセス時にサーバ側でデータを取得しpre-Renderingします。

ブログの記事のようにクライアント側での更新がないページにはgetStaticPropsを利用します。更新が行われるページにはgetServerSidePropsを利用します。getStaticPropsはビルド時にページが準備されているのですぐにページが表示されます。サイト、アプリケーションの高速化を実現するためにこの2つをうまく活用していく必要があります。

getServerSidePropsを利用した方法

getServerSidePropsを利用してデータを取得しページに表示させるために新たにpostsディレクトリを作成してindex.jsファイルを作成します。

作成したindex.jsファイルではgetServerSidePropsの中でJSONPlaceholderからfetchを利用してデータの取得を行っています。まずは正しくデータの取得ができているか確認するためにconsole.logを利用します。


export default function index({ posts }) {
  return (
    <div>
      <h1>POST一覧</h1>
    </div>
  );
}

export async function getServerSideProps() {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  console.log(posts);
  return { props: { posts } };
}

通常のJavaScriptであればコード中にconsole.logを記述するとブラウザのデベロッパーツールのコンソールログに情報が出力されます。しかし、getServerSidePropsはサーバ側(Next.js)で実行されるため、npm run devを実行したターミナルにpostsの100件分のデータが表示されることが確認できます。

getServerSidePropsは名前の通り、ServerSide(サーバーサイド)で実行されるメソッドです。getServerSidePropsはリクエスト毎に実行されます。

データが取得できることが確認できたのであとはindex関数に渡したpropsをmap関数で展開します。


export default function index({ posts }) {
  return (
    <div>
      <h1>POST一覧</h1>
      <ul>
        {posts.map((post) => {
          return <li key={post.id}>{post.title}</li>;
        })}
      </ul>
    </div>
  );
}

export async function getServerSideProps() {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  console.log(posts);
  return { props: { posts } };
}

ブラウザで確認するとgetServerSidePropsを利用して取得したデータがブラウザ上に表示されます。外部にあるリソースからもデータが取得できるようになりました。

POST一覧が表示
POST一覧が表示
getServerSidePropsはページコンポーネントでは実行することができますがページコンポーネント以外の通常のコンポーネント内では実行することができません。ページコンポーネントでデータの取得を行い、propsで他のコンポーネントに取得したデータを渡すことになります。

個別ページの作成と表示

次は、取得後に展開したデータとダイナミックルーティングを使って個別のPOSTデータをどのように取得し表示させるのかを確認していきましょう。

ダイナミックルーティングに対応できるようにpostsの下に[post].jsファイルを作成します。


export default function post() {
  return <h1>POST(投稿)</h1>;
}

/posts/index.jsファイルにLinkをimportしてタイトルをクリックするとPOSTページに遷移するようにコードの更新を行います。


import Link from "next/link";
export default function index({ posts }) {
  return (
    <div>
      <h1>POST一覧</h1>
      <ul>
        {posts.map((post) => {
          return (
            <li key={post.id}>
              <Link href={`/posts/${post.id}`}>
                <a>{post.title}</a>
              </Link>
            </li>
          );
        })}
      </ul>
    </div>
  );
}
//略

POST一覧画面に表示されているタイトルをクリックするとPOSTのページにページの再読み込みを行うことなくページ遷移するか確認を行ってください。ページ遷移後は以下の画面が表示されます。

POSTの個別ページ
POSTの個別ページ

POSTのidの取得

ページを遷移した時に個別ページの内容を表示させるためには、postのIDを取得して再度JSONPlaceholderにアクセスを行い、個別ページの情報を取得する必要があります。

postのIDはgetServerSidePropsのparamsを通して取得することができます。postのIDが取得できるのかどうかconsole.logを利用して確認します。


略
export async function getServerSideProps({ params }) {
  console.log(params);
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  return { props: { posts } };
}

POSTの一覧から個別ページにアクセスするとnpm run devを実行したターミナルにオブジェクトとして表示されます。メッセージが表示されるのはブラウザのコンソールではないことを注意してください。


{ post: '1' }

ダイナミックルーティングを利用しているためでIDの値をparamsで取得しましたが、params以外にもreq, res, queryといった値も取得することができます。それらの情報を確認してみたい場合は、getServerSidePropsの引数にcontextを入れてconsole.log(context)で確認してください。reqはrequestの略でアクセスしたきたクライアント情報やヘッダー情報も確認することができます。


略
export async function getServerSideProps(context) {
  console.log(context);
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  return { props: { posts } };
}
入門者の人にとってはcontextを見ても情報が多すぎてチンプンカンプンだと思うのであまり気にしないでください。さまざまな情報が取得できるものがあるとだけ頭の片隅に置いておいてください。

個別ページのデータ取得

paramsに保存されたIDを利用して個別データの取得を行います。


export default function post({ post }) {
  return (
    <div>
      <h1>POST(投稿){post.id}</h1>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  );
}

export async function getServerSideProps({ params }) {
  const id = params.post;
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const post = await res.json();
  return { props: { post } };
}

ブラウザで確認すると下記のように個別のPOSTデータの内容を表示させることができます。

個別のPOSTデータの取得
個別のPOSTデータの取得

存在しないIDにアクセスがあった場合

URLは手動で変更が行えるためidの値を変更することが可能です。JSONPLACEHOLDERはpostsデータを100件までしか戻してくれないため/posts/101にアクセスした時に何が表示されるのか確認しておきましょう。下記のようにタイトルのみ表示されます。

データの存在しないURLへアクセス
データの存在しないURLへアクセス

console.logを使って存在しないIDにアクセスがあった場合にどのようなデータを取得しているのか確認しておきましょう。


export async function getServerSideProps({ params }) {
  const id = params.post;
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const post = await res.json();
  console.log(post);
  return { props: { post } };
}

npm run devコマンドを実行したターミナルを見ると空のオブジェクト{}であることがわかります。

getServerSidePropsを実行した時returnでpropsを持つオブジェクトを戻していましたがprops以外にもnotFoundを持つオブジェクトを戻すことができます。NotFoundはtrueとfalseの値を持つことができますがfalseに設定するとエラーになります。


return {
  notFound: true,
};

存在しないIDでアクセスした空のオブジェクトになることが確認できているので、空のオブジェクトの判定でnotFoundを持つオブジェクトを戻すように設定を行います。


export async function getServerSideProps({ params }) {
  const id = params.post;
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const post = await res.json();
  if (!Object.keys(post).length) {
    return {
      notFound: true,
    };
  }
  return { props: { post } };
}

再度/posts/101にアクセスすると404ページが表示されます。

404ページが表示
404ページが表示

getStaticPropsを利用した方法

getServerSidePropsはリクエスト毎にサーバ側でデータの取得が行われますがgetStaticPropsはビルド時にデータの取得を行います。

getStaticPropsへの変更

POSTSの一覧を取得するためにpostsディレクトリのindex.jsファイルのgetServerSidePropsをgetStaticPropsに変更を行います。


export async function getStaticProps() {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  return { props: { posts } };
}

npm run devを実行中にファイルを保存した場合は

メソッドを変更しただけではブラウザ上では何も変化がなくPOST一覧が取得できます。

POST一覧が表示
POST一覧が表示

次に[post].jsファイルでもgetServerSidePropsからgetStaticPropsに変更を行います。


export async function getStaticProps({ params }) {
  const id = params.post;
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  const post = await res.json();
  return { props: { post } };
}

index.jsの場合はgetServerSidePropsからgetStaticPropsに変更するだけで同じようにデータが表示されましたが、[post].jsファイルではエラーが表示されます。

エラーの内容はgetStaticPathsがダイナミックSSG(ServerSide Static Generatoin)ページでは必要と記述されています。

エラー:getStaticPathsが必要
エラー:getStaticPathsが必要

getStaticPathsによるパス情報の取得

エラーメッセージに表示されている通りgetStaticPathsメソッドの追加を行います。getStaticPathsのメソッドの中ではビルド時に作成するページのパス一覧を作成して、pathsでreturnする必要があります。

以下がgetStaticPathsメソッドの中身です。JSONPlaceholerにアクセスしてpostsの一覧を取得し取得したpostsをmap関数で展開して個別ページのidを取り出してパス(/posts/${post.id})を作成しています。


//略
export async function getStaticPaths() {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  const paths = posts.map((post) => `/posts/${post.id}`);
  return {
    paths,
    fallback: false,
  };
}

getStaticPathsで個別ページのパス情報が取得できると個別ページにアクセスしてもエラーなしでページが表示されます。

下記のように記述することもできます。


//略
export async function getStaticPaths() {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
  const posts = await res.json();
  const paths = posts.map((post) => ({
    params: { post: post.id.toString() },
  }))
  return {
    paths,
    fallback: false,
  };
}

getStaticPathsは作成するページのパス情報を渡す役割をもっているため、getStaticPathsがない場合はパス情報がないため、先ほどのようなエラーが発生します。

fallbackの設定

fallbackはtrueとfalseを設定することができ、falseに設定した場合は存在するPostのページ以外にアクセスした場合に404エラーが表示されます。下記は存在しないidの101にアクセスを行っています。

fallbackの値がfalseの場合
fallbackの値がfalseの場合

fallbackをtureに設定した場合はサーバエラーの画面が表示されます。ビルド時に存在しなかったページが追加された場合にtureを設定しておくことでそのページにアクセスするとページのビルドが行われ作成されたページをユーザに返すことができます。

fallbackをtrueに設定した場合
fallbackをtrueに設定した場合

npm run buildコマンドの実行

getStaticPropsではビルド時に静的なページが作成されるので実際にnpm run buildを実行してみましょう。実行するとリアルタイムで静的ページが生成されていることが実行のメッセージログから確認することができます。


 % npm run build
//略
info  - Generating static pages (104/104)
//略

作成されるファイルは.next/server/pages/postsの下に1.html, 1.jsonから100.html, 100.jsonまでの200個のファイルと[post].jsファイルが作成されることが確認できます。再度npm run devを実行すると200個のファイルは削除されpostsフォルダには[index].jsファイルのみ残された状態になります。

1.htmlファイルの中身を確認するとブラウザに表示されるそのままの情報が含まれていることが確認できます。

環境変数の設定

.env.localファイル

API_KEYやデータベースへの接続情報などコードに直接記述するのではなく環境変数を利用して設定したい場合は.env.localファイルを利用することができます。

プロジェクトフォルダに.env.localファイルを作成し下記のように環境変数を設定することができます。


API_KEY=myapikey
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword

コード中で利用したい場合はprocess.env.API_KEY、process.env.DB_HOSTで値を取得することができます。

getServerSidePropsで利用したい場合は下記のように利用することができます。


export async function getServerSideProps() {
  const api_key = process.env.API_KEY;
  const result = await fetch(
    `https://end_point_url/?api_key=${api_key}`
  );

.env.localファイルからの値の取得はgetServerSidePropsのようにサーバ側で実行される場合には可能ですがブラウザ側の処理で利用したい場合はNEXT_PUBLIC_を追加する必要があります。


NEXT_PURLIC_API_KEY=myapikey
API_KEY=myapikey
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword

コードで利用する場合もprocess_env.NEXT_PUBLIC_API_KEYから取得します。


  useEffect(() => {
    const fetchData = async () => {
      const api_key = process.env.NEXT_PUBLIC_API_KEY;
同じ環境変数を利用しているのにある処理では取得でき、ある処理では取得できない場合はサーバ側の処理なのかクライアント側(ブラウザ側)で実行される処理なのかを確認してください。区別がつかない場合は取得できないコードでNEXT_PUBLIC_を設定して実行してみてください。

環境変数を追加した場合はnpm run devコマンドを実行している場合は再実行してください。

その他の環境変数

.env.localの他に.env.development(開発環境用), env.production(本番環境用)という名前の環境変数を保存するファイルを作成することができます。もし開発環境で.env.localと.env.developmentに同じ名前の環境変数を追加し異なる値を設定した場合は.env.localによって上書きされます。.env.productionに関しても同様で.env.localによって値は上書きされます。

.env.localファイルはgitでpushしてもアップロードされないように.gitignoreファイルに指定されています。


# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local

.gitignoreファイルには先ほど説明したファイルとは異なる.env.development.local, .env.production.localというファイルを確認することができます。これはそれぞれ開発環境と本番環境で利用することができ、.env.localよりも設定した値は優先されます。

テスト用の.env.test, .env.test.localというファイルも利用することができます。

まとめると開発環境であれば.env.developmentl.localが一番優先度が高く.env.local, .env.developmentと優先度が下がることになります。

styled-jsxによるCSSの適用

styled-JSXを利用してCSSの適用を行っていきます。CSSを適用する方法は複数ありstyled-JSXを利用する必要はありません。

好き嫌いの好みが別れそうですがJavaScriptの関数の中にCSSを記述することができます。見慣れるまでには不自然に感じてしまうかもしれませんが、特別難しいことはありません。早速使用方法を確認してみます。

h1タグの文字色、背景色を変更したい場合は下記のように記述することができます。


export default function Home() {
  return (
    <div>
      <h1>Hello Next.js</h1>
      <style jsx>
        {`
          h1 {
            color: red;
            background: green;
          }
        `}
      </style>
    </div>
  );
}

styleタグの中に{“}を入れるのを忘れてないでください。それ以外は通常のCSSファイルと同様にCSSを記述することができます。

styled-cssの適用方法
styled-cssの適用方法

classNameを利用した適用方法

classを使って適用する場合はclassNameを利用する必要があります。classNameを利用するのはstyled-jsxを利用するときだけに限定されるものではなくJSX内でclassを利用するために使われます。


export default function Home() {
  return (
    <div>
      <h1 className="heading">Hello Next.js</h1>
      <style>
        {`
          .heading {
            color: red;
            background: green;
          }
        `}
      </style>
    </div>
  );
}

Component内での適用について

index.jsファイル内に関数を使ってContentコンポーネントを作成します。Contentコンポーネント内にstyled-jsxを利用してCSSを適用します。


function Content() {
  return (
    <div>
      <p>ここにコンテンツが入ります。</p>
      <style jsx>{`
        p {
          color: blue;
        }
      `}</style>
    </div>
  );
}

ContentコンポーネントではpタグにCSSを適用しているので親側のHomeコンポーネントにもpタグを追加し、適用範囲を確認します。


export default function Home() {
  return (
    <div>
      <h1 className="heading">Hello Next.js</h1>
      <Content />
      <p>ここにもコンテンツが入ります。</p>
      <style>
        {`
          .heading {
            color: red;
            background: green;
          }
        `}
      </style>
    </div>
  );
}

ブラウザで確認した結果、styled-jsxで適用したCSSはContentコンポーネント内のpタグのみに適用されることがわかります。

Contentコンポーネント内のみの適用
Contentコンポーネント内のみの適用

Contentコンポーネント内のみにCSSを適用できるため他のコンポーネントに影響を与えません。そのため作成したコンポーネントを再利用を効率的に行うことができます。もしコンポーネントの外に影響がある場合はCSSの上書きやクラス名の重複などを心配する必要があります。

もしコンポーネント外にstyled-cssで設定したCSSを適用したい場合はglobalをstyleタグに追加することで適用できます。


function Content() {
  return (
    <div>
      <p>ここにコンテンツが入ります。</p>
      <style global jsx>{`
        p {
          color: blue;
        }
      `}</style>
    </div>
  );
}

Cotentコンポーネント外のpタグにもCSSが適用されていることが確認できます。

styleタグにglobalを追加
styleタグにglobalを追加

styled-jsx内で変数を利用

styled-jsxはJSXの中つまりJavaScriptの中に記述しているので変数や関数を利用することも可能です。Contentコンポーネントが親コンポーネントから受け取ったpropsの値を利用して動的に適用するCSSを変更することができます。

propsで受け取ったtypeの文字列がalertであれば文字色は赤、alertでない文字列の場合は青になるように設定しています。

function Content({ type }) { return ( <div> <p>ここにコンテンツが入ります。</p> <style global jsx>{` p { color: ${type == “alert” ? “red” : “blue”}; } `}</style> </div> ); }

<Content type="alert" />

alertを渡すと文字は赤になっています。

propsのtypeでalertを渡す
propsのtypeでalertを渡す

<Content type="warning" />

alert以外を渡すと文字は青になっています。

propsのtypeでwariningを渡す
propsのtypeでwariningを渡す

CSSファイルによるCSSの適用

先ほど述べたようにCSSの適用にはさまざまな方法で行うことができます。stylesディレクトリにあるglobals.cssを利用しても適用することができます。

global.css以外の任意の名前をつけることは可能です。

globals.cssファイルはnext.jsのインストール時から存在するpagesディレクトリの下にある_app.jsファイルの中でimportが行われています。

stylesディレクトリに下に保存されているglobals.cssファイルを開いて.headingを追加します。


.heading {
  color: green;
}

index.jsのh1タグにはheadingクラスが設定されています。


export default function Home() {
  return (
    <div>
      <h1 className="heading">Hello Next.js</h1>
    </div>
  );
}

ブラウザで確認するとglobals.cssに記述したheadingが適用されていることが確認できます。

global.cssファイルによる適用
global.cssファイルによる適用

_app.jsではなくindex.jsファイルでglobals.cssファイルをimportするとエラーが発生します。Global CSS cannot be imported from files other than your Custom . Please move all global CSS imports to pages/_app.tsx….

ではコンポーネントにCSSファイルを使ってCSSを適用したい場合はどのように行うのでしょうか。適用方法について確認を行っていきます。

module.cssファイルによりCSSの適用

stylesディレクトリの下にはNext.jsをインストール後、2つのファイルが保存されています。一つがglobals.cssでもう一つがHome.module.cssです。cssの拡張子の前にmoduleが入っているのがポイントです。

globals.cssが_app.jsからimportしてアプリケーション全体に記述したCSSを適用することができます。XXXX.module.cssファイルはコンポーネントでimportを行うことがファイルです。しかし、importの方法が異なります。


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

適用方法も下記のように行います。

import styles from “../styles/Home.module.css”; export default function Home() { return ( <div> <h1 className={styles.heading}>Hello Next.js</h1> </div> ); }

Home.module.cssファイルは下記のように記述しています。


.heading {
  background-color: red;
}
CSSが適用された画面です。
CSSが適用された画面です。

Tailwind CSSによるCSSの適用

Next.jsでもTailwind CSSを利用することができます。Tailwind CSSのインストール手順についてはTailwind CSSのドキュメントに記載されているのでドキュメントを参考に設定を行います。

npmxコマンドでパッケージのインストールを行います。


 % npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

パッケージのインストール完了後、Tailwind CSSの設定ファイルの作成を行います。


 % npx tailwindcss init -p
  
   tailwindcss 2.1.2
  
   ✅ Created Tailwind config file: tailwind.config.js
   ✅ Created PostCSS config file: postcss.config.js

tailwind.config.js、postcss.config.jsファイルが作成されます。

tailwind.config.jsファイルを開いてpurgeの設定を行ってください。デフォルトではpurgeの設定は[]です。


module.exports = {
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

stylesフォルダにあるglobal.cssファイルにtailwind cssの@tailwindディレクティブを記述します。


@tailwind base;
@tailwind components;
@tailwind utilities;

以上にTailwind CSSを利用するための設定は完了です。Tailwind CSSのフォントサイズのクラスであるtext-3xlを適用するとブラウザ上にはサイズの大きいHello Next.jsが表示されます。


export default function Home() {
  return (
    <div>
      <h1 className="text-3xl">Hello Next.js</h1>
    </div>
  );
}

Tailwind CSSのtext-3xlクラスが設定されていない場合はnpm run devを再実行してください。

_document.jsによるカスタマイズ

Next.jsではindex.jsファイルにheadタグ、bodyタグ、scriptタグなどを記述しなくても自動で設定されています。headタグ、bodyタグなど全ページに共通する設定をカスタマイズしたい場合に利用することができます

カスタマイズを行う前に.pagesフォルダの下に_document.jsファイルを作成してください。作成後下記を記述してください。内容はNext.jsのドキュメントに記載されている内容をコピー&ペーストしているだけです。


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

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <div id="portal"></div>
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

最もシンプルなカスタマイズでhtmlタグにlangを設定しています。


<Html lang="ja">

ブラウザを見ただけでは設定値はわからないのでブラウザからソースを確認してください。langを設定してもブラウザに表示される内容には何も変化はありません。

_document.jsファイルを作成し設定しても反映されない場合はnpm run devを再実行してください。

ソースコードを確認してhtml lang=”ja”を見つけることができれば_document.jsファイルによりカスタマイズが行われていることがわかります。

もし_document.jsファイルのMainタグを削除したらどうなるのかと気になる人もいるかと思います。Mainタグを削除するとindex.jsファイルに記述して内容は表示されなくなります。index.jsファイルの中身はMainタグの部分に表示されるためです。_document.jsに記述したHeadタグ、NextScriptタグは必須なのものなので削除したらエラーによりページが表示されなくなります。

_document.jsファイルはサーバ側で処理が行われるためクリックイベントなどを設定しても実行することはできません。そのかわりモーダルウィンドウなどを利用するためにReact portal機能を使いたい場合はbodyタグの閉じタグの前に<div id=”portal”></div>を追加することができます。


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

class MyDocument extends Document {
  render() {
    return (
      <Html lang="ja">
        <Head />
        <body>
          <Main />
          <div id="portal"></div>
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

ソースを見るとbodyタグの閉じタグの前にはNextSciprtタグによりスクリプトが登録されていますが追加したdiv id=”portal”の要素は<div id=”_next”></div>タグの後ろに追加されることがわかります。

まとめ

外部からのデータの取得方法まで確認を行うことができたので、ブログの記事データなどfetchコマンドで取得することが可能であればここまでの知識でブログサイトの作成も行うことが可能です。しかしSEOのためメタタグの設定等が必要になるため、さらに学習が必要となります。今後本ブログでも設定方法を紹介していきます。