Next.js14 ServerActionsのユースケースと将来性
2023年10月27日に Next.js 14.0.0 がリリースされました。
様々なバグフィックスと機能の追加、安定化が行われておりますが、今回のバージョンで安定版となったServer Actions を追っていきます。
Next.js 14 の要点は ame-x さんの記事でまとめられていますので、そちらをご確認すると良いです。
App Directory
Next.js 13から注目を浴びているApp Directoryは、サーバーサイドレンダリング(SSR)を基本としつつも、クライアントサイドレンダリング(CSR)と共存したコンポーネント開発が可能な仕組みです。
app
ディレクトリに配置される page.tsx
と、独自のコンポーネントは基本的にサーバーサイドレンダリングされるコンポーネントとしてビルドされます。
クライアントサイドのコンポーネントとして構成する場合は、ファイルの冒頭に use client
を指定することで実装が可能です。
`use client`
const ButtonComponent = () => {
return <button>Push me!</button>
}
export default ButtonComponent
SSRとCSRの共存が可能と書きましたが、これら2種類の状態であるコンポーネントを組み合わせたページを構成できます。
これにより、クライアントサイドで動的な動きが必要のないコンポーネントは最小限のコード(html)のみがユーザーにレスポンスされることで、ダウンロードするサイズが軽減されレスポンスが高速化されます。
また、サーバーサイドでのみレンダリングされるコンポーネントでは、Prisma などのORMを使って、直接レンダリングするオブジェクトを取得することができます。
プライベートなプロパティが含まれていたとしても、ユーザーにレスポンスされるものはJSXに指定されたプロパティだけとなり、必然的にユーザーに必要な情報のみに絞り込まれます。
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
const ServerSidePage = async () => {
// DBからUser情報を取得
const user = await prisma.user.findFirst()
// 当然 email情報も含まれるがサーバーサイド内のみ
console.log(user?.email)
// ユーザーにレスポンスされるのはレンダリングされるプロパティのみ
return (
<div>
<p>あなたは {user?.name} です</p>
</div>
)
}
export default ServerSidePage
これがおおまかな App Directory の特徴ですが、本題の Server Actions に進みます。
Server Actions
基本的にサーバーサイドコンポーネントとしてビルドされるApp Directoryですが、すべてがそう単純な構成でつくることはできません。
最も多いユースケースは Form でしょう。
- リアルタイムバリデーション
- エラーメッセージの表示
- valueのstate管理
これらはサーバーサイドで行われる一回のレンダリングでは実現できず、javascriptを使った動作を実装する必要があります。
この場合は、当然ながら use client
を使ったクライアントサイドコンポーネントとして開発することになります。となると、一般的にフォームを送信する処理では、 fetch()
関数などを利用してAPIを経由し、DBに変更を加えることになるのですが、 Server Actionsでは、クライアントサイドコンポーネントコードの一部にサーバーサイドのコードを含めることができます
Server Actionsを使わない場合には、 useHooks
を利用したフェッチと、サーバーサイドでのフェッチ2つのフェッチルートが存在することになり、アプリケーション構造としての複雑性が増してしまいます。
もちろんすべてのフェッチをサーバーサイドで実現することは実務開発の場合非常に難しいでしょうが、少なくとも『メインはサーバーサイドフェッチ』程度の設計にすることは可能になるということです。
フォームによる具体的なユースケース
ではコードを実際に見ていきます。
サインインフォームでメールアドレスとパスワードを入力し送信することを想定しました。
SSRフォーム
const submitAction = async (data: FormData) => {
'use server'
const email = data.get('email')
const password = data.get('password')
console.log(email, password)
}
const ServerSidePage = () => {
return (
<div>
<form name='form' action={submitAction} className='flex flex-col gap-4'>
<input name='email' className='bg-slate-200' />
<input name='password' type='password' className='bg-slate-200' />
<button type='submit'>Submit</button>
</form>
</div>
)
}
export default ServerSidePage
最低限の構造を用意しました。このコードの場合はサーバーサイドコンポーネントのフォームです。
このように use server
を使うことで、 <form>
タグの action
属性に非同期関数を指定し簡単にフォーム送信処理を実装することが可能になっています。
- フォーム送信先の指定が必要ない
- メソッド指定の必要がない
- ファイルを分ける必要がなく、コンポーネント独自の実装として1ファイルにまとめることができる
しかし、これはサンプルであり、実際のユースケースではUX向上のため、クライアンドサイドコンポーネントとしてフォームを開発することが多いはずです。
CSRフォーム
クライアントサイドコンポーネントのファイルには、 use server
を含めた関数を実装しても動作させることができません。ですが、ファイルを分割することでSSRフォームと同様の実装が可能になります。
'use client'
import { submitAction } from './formActions'
const ServerSidePage = () => {
return (
<div>
<form action={submitAction} className='flex flex-col gap-4'>
<input name='email' className='bg-slate-200' />
<input name='password' type='password' className='bg-slate-200' />
<button type='submit'>Submit</button>
</form>
</div>
)
}
export default ServerSidePage
'use server'
export const submitAction = async (data: FormData) => {
const email = data.get('email')
const password = data.get('password')
console.log(email, password)
}
フォームのレンダリングはクライアントサイドで行われていますが、 action
属性で指定された先の関数はサーバーサイドで実行されるため、コードもサーバーサイド内で完結され隠蔽されている状態を作ることができます。
console.log
を配置するとわかりやすいですが、ブラウザのコンソールではログ出力されず、IDE上のコンソールに標準出力されるのが確認できます。
このように、 Server Actions は、サーバーサイドで実行したい処理のみを、クライアントサイドから分離することができるのです。
なにが便利なの?
実装方法も機能もわかりましたが、一体なにが便利なんでしょうか。
上記にも一部記載しましたが、サーバーサイドでデータをフェッチするメリットはいくつかあります。
- DBから直接フェッチできる
- フェッチする先と物理的に近い位置で通信できる
- カスタムHookの作成、RestAPIといった中間コードが不要になる
実際Server Actionsを利用すると、Next.jsのみでバックエンド処理を最低限のコードで実装できていることがわかると思います。
またAWSやGCPなどのクラウドサービスの場合、 Next.jsの動いているサーバーとデータベースサービスのネットワークは同クラウドのネットワーク内にあるため、非常に高速で安定した通信を行えます。
その構造ってPHPじゃない?
はい。そうなんです。
ただ、明確に違う点は、クライアントで処理する場合だけ必要な分のjavascriptをクライアントにレスポンスする点です。
PHPやLaravel Bladeといったテンプレート構造の場合は、フロントのUXに必要なアニメーションライブラリのコードをミニマイズしページに読み込む方法が取られますが、Next.jsのApp Directoryでは、必要なページまたはコンポーネントに対してのみライブラリコードが含まれるようになるため、非常に軽量です。
また、SSRとCSRが混在し一見複雑に見えますが、javascriptまたはtypescriptといった言語で統一した書き方ができ、ファイルも柔軟に分離することができるため、慣れると快適なのは間違いないです。
セキュリティ大丈夫?
Server Actionsを含め、サーバーサイドでレンダリングされるファイルや関数は、クライアントサイドで実行されるような記述をすると、ビルド前にエラーが吐き出される仕組みです。
これによりサーバーサイドのコードを露出しないように工夫されています。
また、npmパッケージである server-only
パッケージをファイルにインポートすることで、Next.js外のサーバー専用ファイルを用意することも可能です。
import 'server-only'
export const serverLogic = () => {
console.log('クライアントサイドで呼び出すことはできない')
}
ですので、Next.jsのビルドが通っていればそれは安全にサーバーサイド処理が実装されている。ということがある程度保証されるわけです。
しかし、懸念点は多くある
非常に便利なNext.jsのServer Actionsですが、懸念点はあります。
マルチプラットフォームです。
実務でサービスを展開することになった場合、Webブラウザ以外にも多くのプラットフォームで利用できるようにアプリを開発することは少なく有りません。
- Windows
- Mac
- iPhone
- Android
特にモバイルは顕著です。予算が膨大であれば、 Next.jsとFlutterといった構成も可能でしょうが、素早く展開したい場合は Next.jsのコードを再利用するために、 React Native などを採用したいところです。
しかし、 Server Actionsはエンドポイントを持ちません。あくまでNext.js内で利用される機能として実装することになります。
これは私自身もまだ検証の段階ですが、このようなNext.jsで閉じてしまうコードをどのように活用していくかは今後の課題になっていくでしょう。Next.jsチームはどのように考えているのかな?
iOS・Android・WebでUIを実装し、APIゲートウェイを使ってバックエンドを共通化することなんて普通にありますよね? その場合Server Actionsは微妙な選択肢です。
いまのところ、スタートアップのプロトタイプ開発といったまずは素早く開発をしてシードラウンドを乗り切る場合や、予算のない中でまずはWebサービスとして展開し、後にネイティブモバイルアプリを開発するといった用途には使えそうかなと思っています。
Server Actionsも結局はただのバックエンド関数ではあるので、将来的にAPIサービスにコピペするといったことは可能ですから、まったく再利用できないということではないからです。
PWAがもっとネイティブよりになってくれれば最高なんですけど、アプリストアが犠牲になるわけですから、ビジネス的にも今のところ難しいでしょうね……。
最後に
Next.js 13からのApp Directoryの流れは界隈でも様々な意見が飛び交っているようで、また新しい混沌の時代がやってきたのかなという雰囲気も見られます。
これがWebにおいて過去の辛い時代と同じようにならずに、良い方向に進んでいくことを僕は願いながら見守っていきたいと思います。
弊社 Nickel Lab. では、Next.jsを使ったWebアプリケーションの開発を得意としており、Web3にも精通しています。
- ブロックチェーンのDappsを作りたい
- Webプロトタイプを作りたい
- 知見がないのでコンサルしてほしい
といったご相談がございましたら、以下Twitterか弊社問い合わせまでご連絡下さい。
どもでした〜
Discussion