Nxを使用したNext.jsアプリケーションの最適化
公開: 2022-03-10この記事では、Nxとその豊富な機能を使用して高性能のNext.jsアプリケーションを最適化および構築する方法について説明します。 Nxサーバーをセットアップする方法、既存のサーバーにプラグインを追加する方法、および実用的な視覚化を備えたモノレポの概念について説明します。
アプリケーションを最適化し、アプリケーション間で再利用可能なコンポーネントを効果的に作成することを検討している開発者の場合、この記事では、アプリケーションをすばやくスケーリングする方法と、Nxを操作する方法について説明します。 従うには、Next.jsフレームワークとTypeScriptの基本的な知識が必要です。
Nxとは何ですか?
Nxは、あらゆる規模での設計、テスト、およびビルドを支援するオープンソースのビルドフレームワークです。堅牢なコマンドラインインターフェイス(CLI)、キャッシング、および依存関係の管理を提供しながら、最新のテクノロジーやライブラリとシームレスに統合します。 Nxは、最新のフレームワーク、テスト、およびツール用の高度なCLIツールとプラグインを開発者に提供します。
この記事では、NxがNext.jsアプリケーションとどのように連携するかに焦点を当てます。 Nxは、サイプレス、ストーリーブック、スタイル付きコンポーネントなど、Next.jsアプリケーションでテストおよびスタイリングするための標準ツールを提供します。 Nxは、アプリケーションのモノリポジトリを容易にし、複数のアプリケーションのソースコードとライブラリを保持できるワークスペースを作成して、アプリケーション間でリソースを共有できるようにします。
なぜNxを使用するのですか?
Nxは、アプリケーションのエンドツーエンド(E2E)テスト用の定型文、スタイリングライブラリ、モノレポなど、すぐに使用できる妥当な量の機能を開発者に提供します。
Nxを使用することには多くの利点があります。このセクションでは、それらのいくつかについて説明します。
- グラフベースのタスク実行
Nxは、分散グラフベースのタスク実行と計算キャッシュを使用して、タスクを高速化します。 システムは、グラフシステムを使用してタスクとコマンドをスケジュールし、各タスクを実行するノード(つまりアプリケーション)を決定します。 これにより、アプリケーションの実行が処理され、実行時間が効率的に最適化されます。 - テスト
Nxは、単体テストおよびE2Eテスト用の事前構成されたテストツールを提供します。 - キャッシング
Nxは、キャッシュされたプロジェクトグラフも保存します。 これにより、更新されたファイルのみを再分析できます。 Nxは、最後のコミット以降に変更されたファイルを追跡し、それらのファイルに対してのみアクションをテスト、ビルド、および実行できるようにします。 これにより、大規模なコードベースで作業しているときに適切な最適化が可能になります。 - 依存関係グラフ
視覚的な依存関係グラフを使用すると、コンポーネントが相互にどのように相互作用するかを調べることができます。 - クラウドストレージ
NxはクラウドストレージとGitHubの統合も提供するため、チームメンバーとリンクを共有してプロジェクトログを確認できます。 - コードシェア
プロジェクトごとに新しい共有ライブラリを作成することは、非常に負担になる可能性があります。 Nxはこの複雑さを解消し、アプリのコア機能に集中できるようにします。 Nxを使用すると、アプリケーション間でライブラリとコンポーネントを共有できます。 フロントエンドアプリケーションとバックエンドアプリケーションの間で再利用可能なコードを共有することもできます。 - モノリポジトリのサポート
Nxは、複数のアプリケーションに1つのワークスペースを提供します。 この設定では、1つのGitHubリポジトリに、ワークスペースの下にあるさまざまなアプリケーションのコードソースを格納できます。
公開可能なライブラリのNx
Nxを使用すると、公開可能なライブラリを作成できます。 これは、monorepoの外部で使用するライブラリがある場合に不可欠です。 Nx Storybookを統合して組織のUIコンポーネントを開発している場合、Nxはストーリーと一緒に公開可能なコンポーネントを作成します。 公開可能なコンポーネントは、これらのコンポーネントをコンパイルして、外部レジストリに展開できるライブラリバンドルを作成できます。 モノリポジトリでのみ使用されるライブラリを生成するために使用される--buildable
とは異なり、ライブラリを生成するときに--publishable
オプションを使用します。 Nxは、公開可能なライブラリを自動的にデプロイしません。 nx build mylib
( mylib
はライブラリの名前)などのコマンドを使用してビルドを呼び出すことができます。これにより、外部レジストリにデプロイできる最適化されたバンドルがdist
/ mylib
フォルダーに生成されます。
Nxには、Next.jsをプリセットとして使用して新しいワークスペースを作成するオプション、またはNext.jsを既存のワークスペースに追加するオプションがあります。
Next.jsをプリセットとして新しいワークスペースを作成するには、次のコマンドを使用できます。
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
このコマンドは、「todo」という名前のNext.jsアプリと、スタイリングライブラリとしてstyled-components
を使用して新しいNxワークスペースを作成します。
次に、次のコマンドを使用して、Next.jsアプリケーションを既存のNxワークスペースに追加できます。
npx nx g @nrwl/next:app
Next.jsおよびNxアプリケーションの構築
Next.jsのNxプラグインには、Next.jsアプリケーションを実行および最適化するためのツールとエグゼキューターが含まれています。 開始するには、 next
をプリセットとして新しいNxワークスペースを作成する必要があります。
npx create-nx-workspace happynrwl \ --preset=next \ --style=styled-components \ --appName=todo
上記のコードブロックは、新しいNxワークスペースとNext.jsアプリケーションを生成します。 NxCloudを使用するためのプロンプトが表示されます。 このチュートリアルでは、「いいえ」を選択し、依存関係がインストールされるのを待ちます。 これが完了すると、次のようなファイルツリーが作成されます。
happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-e2e ┃ ┗ .gitkeep ┣ libs ┣ node_modules ┣ tools ┣ .editorconfig ┣ .eslintrc.json ┣ .gitignore ┣ .prettierignore ┣ .prettierrc ┣ README.md ┣ babel.config.json ┣ jest.config.js ┣ jest.preset.js ┣ nx.json ┣ package-lock.json ┣ package.json ┣ tsconfig.base.json ┗ workspace.json
apps
フォルダーには、Next.jsアプリケーション「todo」があり、to-doアプリ用に事前構成されたE2Eテストが含まれています。 これはすべて、強力なNxCLIツールを使用して自動生成されます。
アプリを実行するには、 npx nx serve todo
コマンドを使用します。 アプリの提供が完了すると、次の画面が表示されます。
APIの構築
この時点で、ワークスペースを設定しました。 次は、Next.jsアプリケーションで使用するCRUDAPIを構築します。 これを行うには、Expressを使用します。 monorepoのサポートを示すために、ワークスペース内のアプリケーションとしてサーバーを構築します。 まず、次のコマンドを実行して、Nx用のExpressプラグインをインストールする必要があります。
npm install --save-dev @nrwl/express
これが完了すると、提供されたワークスペースでExpressアプリをセットアップする準備が整います。 Expressアプリを生成するには、次のコマンドを実行します。
npx nx g @nrwl/express:application --name=todo-api --frontendProject=todo
コマンドnx g @nrwl/express:application
は、追加の仕様パラメーターを渡すことができるExpressアプリケーションを生成します。 アプリケーションの名前を指定するには、 --name
フラグを使用します。 Expressアプリを使用するフロントエンドアプリケーションを示すには、ワークスペース内のアプリの名前を--frontendProject
に渡します。 Expressアプリでは、他にもいくつかのオプションを利用できます。 これが完了すると、 todo-api
フォルダーが追加されたapps
フォルダーのファイル構造が更新されます。
happynrwl ┣ apps ┃ ┣ todo ┃ ┣ todo-api ┃ ┣ todo-e2e ┃ ┗ .gitkeep …
todo-api
フォルダーは、 main.ts
エントリファイルを含むExpressボイラープレートです。
/** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser app.get('/api', (req, res) => { res.send({ message: 'Welcome to todo-api!' }); }); const port = process.env.port || 3333; const server = app.listen(port, () => { console.log(`Listening at http://localhost:${port}/api`); }); server.on('error', console.error);
このアプリ内にルートを作成します。 まず、アプリ宣言のすぐ下にある2つのキーと値のペアであるitem
とid
を使用してオブジェクトの配列を初期化します。
/** * This is not a production server yet! * This is only minimal back end to get started. */ import * as express from 'express'; import {v4 as uuidV4} from 'uuid'; const app = express(); app.use(express.json()); // used instead of body-parser let todoArray: Array<{ item: string; id: string }> = [ { item: 'default todo', id: uuidV4() }, ]; …
次に、 app.get()
ですべてのToDoリストをフェッチするルートを設定します。
… app.get('/api', (req, res) => { res.status(200).json({ data: todoArray, }); }); …
上記のコードブロックは、 todoArray
の現在の値を返します。 その後、配列からTo Doアイテムを作成、更新、および削除するためのルートがあります。
… app.post('/api', (req, res) => { const item: string = req.body.item; // Increment ID of item based on the ID of the last item in the array. let id: string = uuidV4(); // Add the new object to the array todoArray.push({ item, id }); res.status(200).json({ message: 'item added successfully', }); }); app.patch('/api', (req, res) => { // Value of the updated item const updatedItem: string = req.body.updatedItem; // ID of the position to update const id: string = req.body.id; // Find index of the ID const arrayIndex = todoArray.findIndex((obj) => obj.id === id); // Update item that matches the index todoArray[arrayIndex].item = updatedItem res.status(200).json({ message: 'item updated successfully', }); }); app.delete('/api', (req, res) => { // ID of the position to remove const id: string = req.body.id; // Update array and remove the object that matches the ID todoArray = todoArray.filter((val) => val.id !== id); res.status(200).json({ message: 'item removed successfully', }); }); …
新しいやることアイテムを作成するために必要なのは、文字列としての新しいアイテムの値だけです。 サーバー上の配列の最後の要素のIDをインクリメントしてIDを生成します。 既存のアイテムを更新するには、アイテムの新しい値と更新するアイテムオブジェクトのIDを渡します。 サーバーでは、 forEach
メソッドを使用して各アイテムをループし、IDがリクエストで送信されたIDと一致する場所でアイテムを更新します。 最後に、配列からアイテムを削除するために、リクエストとともに削除するアイテムのIDを送信します。 次に、配列をフィルタリングし、リクエストで送信されたIDと一致しないすべてのアイテムの新しい配列を返し、新しい配列をtodoArray
変数に割り当てます。
注: Next.jsアプリケーションフォルダーを見ると、以下の構成のproxy.conf.json
ファイルが表示されます。
{ "/api": { "target": "http://localhost:3333", "secure": false } }
これによりプロキシが作成され、 /api
に一致するルートへのすべてのAPI呼び出しがtodo-api
サーバーをターゲットにできるようになります。
Nxを使用したNext.jsページの生成
Next.jsアプリケーションでは、新しいページ、 home
、およびアイテムコンポーネントを生成します。 Nxは、ページを簡単に作成するためのCLIツールを提供します。
npx nx g @nrwl/next:page home
このコマンドを実行すると、ページに使用するスタイリングライブラリを選択するためのプロンプトが表示されます。 この記事では、 styled-components
を選択します。 出来上がり! 私たちのページが作成されます。 コンポーネントを作成するには、 npx nx g @nrwl/next:component todo-item
;を実行します。 これにより、 todo-item
コンポーネントをcomponent
フォルダーが作成されます。
Next.jsアプリケーションでのAPI消費
各ToDoアイテムには、ToDoアイテムを編集および削除するための2つのボタンがあります。 これらのアクションを実行する非同期関数は、ホームページから小道具として渡されます。
… export interface TodoItemProps { updateItem(id: string, updatedItem: string): Promise<void>; deleteItem(id: string): Promise<void>; fetchItems(): Promise<any>; item: string; id: string; } export const FlexWrapper = styled.div` width: 100%; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-top: 20px; @media all and (max-width: 470px) { flex-direction: column; input { width: 100%; } button { width: 100%; } } `; export function TodoItem(props: TodoItemProps) { const [isEditingItem, setIsEditingItem] = useState<boolean>(false); const [item, setNewItem] = useState<string | null>(null); return ( <FlexWrapper> <Input disabled={!isEditingItem} defaultValue={props.item} isEditing={isEditingItem} onChange={({ target }) => setNewItem(target.value)} /> {!isEditingItem && <Button onClick={() => setIsEditingItem(true)} > Edit </Button>} {isEditingItem && <Button onClick={async () => { await props.updateItem(props.id, item); //fetch updated items await props.fetchItems(); setIsEditingItem(false) }}> Update </Button>} <Button danger onClick={async () => { await props.deleteItem(props.id); //fetch updated items await await props.fetchItems(); }} > Delete </Button> </FlexWrapper> ); }
更新機能については、 isEditingItem
の状態がfalse
の場合に無効になる入力があります。 「編集」ボタンをクリックすると、 isEditingItem
の状態がtrue
に切り替わり、「更新」ボタンが表示されます。 ここでは、入力コンポーネントが有効になっており、ユーザーは新しい値を入力できます。 「更新」ボタンをクリックすると、渡されたパラメーターを使用してupdateItem
関数が呼び出され、 isEditingItem
がfalse
に戻ります。
home
コンポーネントには、CRUD操作を実行する非同期関数があります。
… const [items, setItems] = useState<Array<{ item: string; id: string }>>([]); const [newItem, setNewItem] = useState<string>(''); const fetchItems = async () => { try { const data = await fetch('/api/fetch'); const res = await data.json(); setItems(res.data); } catch (error) { console.log(error); } }; const createItem = async (item: string) => { try { const data = await fetch('/api', { method: 'POST', body: JSON.stringify({ item }), headers: { 'Content-Type': 'application/json', }, }); } catch (error) { console.log(error); } }; const deleteItem = async (id: string) => { try { const data = await fetch('/api', { method: 'DELETE', body: JSON.stringify({ id }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; const updateItem = async (id: string, updatedItem: string) => { try { const data = await fetch('/api', { method: 'PATCH', body: JSON.stringify({ id, updatedItem }), headers: { 'Content-Type': 'application/json', }, }); const res = await data.json(); alert(res.message); } catch (error) { console.log(error); } }; useEffect(() => { fetchItems(); }, []); …
上記のコードブロックには、サーバーからfetchItems
を返すtodoArray
があります。 次に、文字列をcreateItem
関数があります。 パラメータは、新しいToDo項目の値です。 updateItem
関数は、更新するアイテムのIDとupdatedItem
値の2つのパラメーターを取ります。 また、 deleteItem
関数は、渡されたIDに一致するアイテムを削除します。
To Doアイテムをレンダリングするために、 items
の状態をマップします。
… return ( <StyledHome> <h1>Welcome to Home!</h1> <TodoWrapper> {items.length > 0 && items.map((val) => ( <TodoItem key={val.id} item={val.item} id={val.id} deleteItem={deleteItem} updateItem={updateItem} fetchItems={fetchItems} /> ))} </TodoWrapper> <form onSubmit={async(e) => { e.preventDefault(); await createItem(newItem); //Clean up new item setNewItem(''); await fetchItems(); }} > <FlexWrapper> <Input value={newItem} onChange={({ target }) => setNewItem(target.value)} placeholder="Add new item…" /> <Button success type="submit"> Add + </Button> </FlexWrapper> </form> </StyledHome> ); …
これでサーバーとフロントエンドがセットアップされました。 npx nx serve todo-api
を実行することでAPIアプリケーションを提供でき、Next.jsアプリケーションの場合はnpx nx serve todo
nxservetodoを実行します。 「続行」ボタンをクリックすると、デフォルトのやること項目が表示されたページが表示されます。
これで、動作するNext.jsとExpressアプリケーションが1つのワークスペースで連携して動作するようになりました。
Nxには、ターミナル実行時にアプリケーションの依存関係グラフを表示できる別のCLIツールがあります。 npx nx dep-graph
を実行すると、次の画像のような画面が表示され、アプリケーションの依存関係グラフが示されます。
Nx用の他のCLIコマンド
nx list
現在インストールされているNxプラグインを一覧表示します。-
nx migrate latest
package.json
内のパッケージを最新バージョンに更新します。 -
nx affected
影響を受けるアプリまたは変更されたアプリに対してのみアクションを実行します。 -
nx run-many --target serve --projects todo-api,todo
リストされているすべてのプロジェクトに対してtargetコマンドを実行します。
結論
Nxの一般的な概要として、この記事ではNxが提供するものと、Nxがどのように作業を容易にするかについて説明しました。 また、NxワークスペースでNext.jsアプリケーションを設定し、既存のワークスペースにExpressプラグインを追加し、monorepo機能を使用してワークスペースに複数のアプリケーションを格納する方法についても説明しました。
完全なソースコードはGitHubリポジトリにあります。 Nxの詳細については、Next.jsのドキュメントまたはNxドキュメントを確認してください。