🍀

Next.js(14.0.4)を触ってみたお話

2024/01/05に公開

ここ2日間、Next.jsを触っています。
Vue系統のNuxtは触っているけど、React系の子は触ってないなぁってことで、 Next.jsを触ってみたという次第。

Next全くわからん状態から、プロジェクトを作り、ファイルを配置そのたもろもろを経て、カメラ取得までやってみた
最後にVercelでデプロイするまで行なってみた。そんな記事。

Nextのバージョンは14.0.4を使用。(調べているうちに古いバージョンの情報と新しい情報がごっちゃになってややこしかったので書いておきます)

Nextで何をしたのか

今回Nextを使うにあたって挑戦してみたのは以下の5つ。

  1. Nextでプロジェクトを作成する
  2. appフォルダ内にファイルを用意してページを作る
  3. 双方向バインディングをやってみる
  4. カメラ取得コンポーネントを作って表示してみる
  5. Vercelでデプロイしてみる

5つもあるので一気に駆け抜けていこう

1. Nextでプロジェクトを作成する

NextはNodeサーバー内での処理、いわゆるSSR(サーバーサイドレンダリング)を用いるため、前提としてNodeが必要となる。
その辺の準備が面倒だったのでGitpodのデフォルト環境を使用しました。
空リポジトリを作って、URLの頭にhttps://gitpod.io#をつけてアクセスして新規ワークスペースをいい感じに作りました。

さて、できたワークスペースにはNodeがすでに入っているので面倒な環境構築いらずで、コマンド一つでNextプロジェクトを作成します。

npx create-next-app testappnext

コマンドを実行すると、以下のように質問されるのでひとまず全部規定値で作成しました

  • nextのプロジェクト作成コマンドを実行してOK?
  • TypeScriptを使う?(規定値はyes)
  • ESLintを使う?(yes)
  • TailWind CSSを使う?(yes)
  • src/ディレクトリを使う?(no)
  • App Routerを使う?(yes)
  • 規定のインポートエイリアス(@/*)を使う?(no)

こうするとプロジェクトフォルダtestappnextが出来上がるので、そこに移動してdevサーバーを起動すると、3000番ポートでサーバーが起動する。やったね

cd testappnext
npm run dev

2. appフォルダ内にファイルを用意してページを作る

さて、サーバー起動まで漕ぎ着けたが、Nextではどのようにページを作ろうかという問題がある。
(ちなみに筆者はVue系と同じように適当なファイル名で配置した結果わけがわからないことになってしまった。残念)
ここでNextのバージョンが13以降だと、App Routerが推奨されており、決まった名前のファイルを作る必要があるようだ。
例として、hogehoge.com/cameraというルーティングでサイトのページを作りたいときは、appフォルダの中にcameraフォルダを作ることになる。

そしてそのフォルダの中に、page.tsxという特別な名前のファイルを作成する
具体的にはこんな感じ

testappnext
├─app
│ └─camera
│   └─page.tsx(`/camera`ページ用)
├─page.tsx(トップページ用)
...

このファイルにページとして出力するコードをかいていくという寸法のようだ。

今回は初めてなので元から用意されているトップページを真似て、以下のように作ってみることにする

export default function Home() { 
	return(<div><ページとしてレンダリングする内容><div>)
}

ページとしてレンダリングする内容を適当に編集して、3000番ポートの/cameraパスにアクセスすると実際に画面表示がされて、ホットリロードも機能したところまで確認した。

3. 双方向バインディングをやってみる

さて、無事にページを表示することまでは完了した。
Vue系になれている筆者はあの簡単双方向バインディングが大好きなのでNextでも同じことをやっていこうと思う。

Nextではサーバーサイドであらかじめ処理をして軽くしようという思想があるようなので、変数はサーバー側で処理を行っている。
そんなわけで、Nextでの双方向バインディングでページをユーザー側のブラウザで更新したかったら、"このファイルはユーザー側のブラウザで行う処理を書きます"という旨を宣言する必要があるらしい。なるほど。

page.tsxファイルの1行目に"use client"という記述を追加する。

そしてもう一つ、双方向バインドを行いたい場合はuseStateという機能を使うらしい。
まずはインポート。

import {useState} from "react"

双方向バインドするための特殊な変数はこのuseStateを使って生成する。

const [huga, sethuga]=useState<Strin>("<hugaの初期値>")

このコードではhugaという文字の変数と、sethugaという名前の関数が作成できる。

このsethuga関数は、sethuga("<更新するhugaの内容>")のようにしてレンダリングされる内容を変化させることができる。

変化させる変数hugaは、レンダリングするreturn{<変数名>}の形で記述してあげるといい感じ。
実際に書いたコードはこれ

"use client"
import {useState, useRef, useCallback, useEffect} from "react"

export default function Home() { 
  const [huga, sethuga]=useState<String>("")
  
  function change_huga(e: any){
    sethuga(e.target.value)
  }

  return (
    <div>
      <span>双方向バインドテスト</span><input style={{color: "black"}} onChange={change_huga} type="text"></input>
      <div>{huga}</div>
    </div>
    )
}

HTML中にInputを用意してあげて、それのonChangeイベントで、内容が変化したらInput内容をsethugaで代入してあげています。
(ネイティヴJSでは、onchangeが全部小文字のところを、Nextで関数を渡したいときはキャメルケースで大文字を混ぜるらしい)

表示されたページのInputに入力するとその内容が反映してくれた。やったね。

専用の関数立てたり、Objectを渡せなかったりするのでちょっと面倒かなと思う反面、双方向するもの/しないものを明確にできる分、効率化やってるって感じがしてかっこいいね(?)

4. カメラ取得コンポーネントを作って表示してみる

さらっと書いているけど、初学者的にはコンポーネント作成+カメラ取得の2つの内容が混じっているこのセクション。まずはコンポーネント化からやってみる。

まずはコンポーネントのファイルを作成します。
Nextでは相対パスでimportするのでフォルダ構成はこんな感じ。

testappnext
├─app
│ └─camera
│   ├─page.tsx
│   └─CameraGet.tsx(追加したコンポーネント)
├─page.tsx
...

そしてpage.tsxでインポート。

import CameraGet from "./CameraGet"

インポートできたらreturn内でHTMLタグのように使えるのでそのまま記載。なんだか既視感。

return (
    <div>
      <span>双方向バインドテスト</span><input style={{color: "black"}} onChange={change_huga} type="text"></input>
      <div>{huga}</div>
      <CameraGet />←追加したコンポーネント
    </div>
    )

ちなみにここで、コンポーネント名は大文字で始めないと読み込んでくれないらしい。へー

とまぁこれでコンポーネントを表示できるわけなので、コンポーネントの中身を作っていこう。
作成したファイルがこれ

"use client"

import {useEffect} from 'react'

export default function CameraGet(){


    useEffect(()=>{
      const elm_video=document.getElementById("camera")! as HTMLVideoElement

      navigator.mediaDevices.getUserMedia(
        {
          audio: false,
          video: {
            facingMode: {exact: "environment"}
        }
        })
        .then((stream)=>{
            elm_video.srcObject=(stream)
            console.log(stream.getTracks())
            elm_video.play()
          }
        )
    }, [])

    return(
        <div>
            <video id="camera" />
        </div>
    )
}

カメラの動きとしてはvideoタグのHTMLエレメントに取得したカメラ画像を貼り付けて表示する形になるいつもの(?)感じなわけだが、getElementByIdってそもそもレンダリング後に動かさなきゃいけないわけです。
そりゃぁ、Elementが描画された後でないと、探そうにも見つかるわけなくエラーになるわけです。

returnで返り値としてhtmlタグを書いているのでこのままでいくと取得できるhtmlElementが無なのでnullになってしまう。

なので、レンダリング後に動作させる関数として設定しなければなりません。
ここで使うのがuseEffect。これはレンダリング後に1回、そして第2引数に書いた変数のいずれかが更新された時に発火するというものらしい。
ここではレンダリング後に1回だけ動けばいいので、第2引数は空配列です。

第1引数とした関数ではvideoタグをidで取得して、iPadの背面カメラを取得してその映像をvideoのhtml Elementに適用するだけです。(Nextの中身の内容でないのではしょり)

iPadで見ると背面カメラの映像が映ってくれた。やったね

(そのへんにあったチョコレート)

5. Vercelでデプロイしてみる

ここまでで、カメラを表示するだけのページが完成したので、どこかしかにデプロイしてみようとい流れになります。なるんです。

NextはVercelというところが作っているらしく、そのVercelは Nextをデプロイするサービスも行っているらしい。
要するに公式のどでかい波があるのでそれに乗ってみようと思う。

まずはVercelのサイトにアクセス。
おあつらえ向けにDeployボタンがあるのでクリック。Githubにコードをあげているので、流れに任せてGithubでログイン。

Githubのリポジトリを取得するための権限設定を行なってリポジトリをインポート。
わたしは念の為Selected Repository(選んだリポジトリ)のみの権限から、今回作ったテストリポジトリのみの権限を与えておきました。

プロジェクト名とフレームワーク(ドロップダウンでNext.jsを指定)を設定。
root Directoryではプロジェクトフォルダ(今回ではtestappnext)を選択してDeployボタンを押してしばらく待つだけでデプロイができてしまいます。
あれ?これだけ?ってなるくらい簡単。

プロジェクト名を含むURLが払い出されるので、別端末からもアクセスできることを確認して、完成。

なんかこう、コードの方でファイルの配置どうすればいいの?とか、レンダリングがサーバーサイド??とか、レンダリング後に処理したいけどどうしたらいい??とか考えることがいっぱいだったのに、デプロイは何のよどみもなく終わってしまって、すごいなぁっておもいます。

Vercelすごーいってなったところで、この記事はこれでおしまい

Discussion