ホームページ >ウェブフロントエンド >jsチュートリアル >Next.js の詳細: 高度な機能を備えた Notes アプリの構築

Next.js の詳細: 高度な機能を備えた Notes アプリの構築

DDD
DDDオリジナル
2024-11-03 15:07:031092ブラウズ

Next.js Deep Dive: Building a Notes App with Advanced Features## 概要と目的

このブログ記事では、実際のシナリオで必要となる最も重要な Next.js 機能について説明したいと思います。

このブログ記事は、私自身と興味のある読者のための単一の参考資料として作成しました。 nextjs ドキュメント全体を読む必要はありません。 nextjs の重要な 実用的な機能をすべて備えた凝縮されたブログ記事を作成し、定期的にアクセスして知識を更新することが容易になると思います。

メモ アプリケーションを並行して構築しながら、以下の機能を一緒に見ていきます。

  • アプリルーター

    • サーバーコンポーネント
    • クライアントコンポーネント
    • ネストされたルーティング
    • 動的ルート
  • ロードとエラー処理

  • サーバーアクション

    • サーバーアクションの作成と使用
    • サーバーアクションとクライアントコンポーネントの統合
  • データのフェッチとキャッシュ

    • サーバー側キャッシュに不安定なキャッシュを使用する 1. revalidateTag を使用してキャッシュを再検証する
    • サーバー側のキャッシュに不安定なキャッシュを使用する
  • ストリーミングとサスペンス

    • loading.tsx を使用したページレベルのストリーミング
    • サスペンスを使用したコンポーネントレベルのストリーミング
  • 並行ルート

    • 名前付きスロットの作成と使用
    • 複数のページコンポーネントの同時レンダリングの実装
  • エラー処理

    • error.tsx によるエラー境界の実装

アプリケーション コードを記録する最終的なメモは次のようになります:

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

この Github リポジトリの spthacode にある最終コードに直接ジャンプしてください。

それでは、これ以上面倒なことはせずに、始めましょう!

Next.js Deep Dive: Building a Notes App with Advanced Features

主要な概念

メモ アプリケーションの開発に入る前に、先に進む前に知っておくべき重要な nextjs の概念をいくつか紹介したいと思います。

アプリルーター

App Router は、従来の "/page" ディレクトリでは不可能だった多くの機能をサポートする新しいディレクトリ "/app" です。 :

  1. サーバーコンポーネント
  2. 共有レイアウト:layout.tsx ファイル。
  3. ネストされたルーティング: フォルダーをフォルダー内にネストできます。ページ パス URL は、同じフォルダーのネストに従います。たとえば、[noteId] 動的パラメータが /app/notes/[noteId]/edit/page.tsx >"1""/notes/1/edit.

  4. /loading.tsx ファイル。ページがユーザーのブラウザーにストリーミングされるときにレンダリングされるコンポーネントをエクスポートします。

  5. /error.tsx ファイル。ページがキャッチされないエラーをスローしたときにレンダリングされるコンポーネントをエクスポートします。

  6. メモ アプリケーションを構築する際に使用する並列ルーティングと多くの機能。

サーバーコンポーネントとクライアントコンポーネント

Nextjs

/app Router に触れる前に、誰もがマスターすべき非常に重要なトピックに飛び込んでみましょう。

サーバーコンポーネント

サーバー コンポーネントは、基本的にサーバー上でレンダリングされるコンポーネントです。

"use client" ディレクティブが前に付いていないコンポーネントは、デフォルトでページやレイアウトを含むサーバー コンポーネントになります。

サーバーコンポーネントは、任意のnodejs API、またはサーバー上で使用される任意のコンポーネントと対話できます。

クライアント コンポーネントとは異なり、サーバー コンポーネントの前に async キーワードを付けることができます。したがって、任意の非同期関数を呼び出して、コンポーネントをレンダリングする前にそれを待つことができます。

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

なぜサーバー上でコンポーネントを事前にレンダリングする必要があるのか​​と疑問に思われるかもしれません。

その答えは、

SEO、パフォーマンスとユーザー エクスペリエンスという短い言葉で要約できます。

ユーザーがページにアクセスすると、ブラウザは HTML、CSS、JavaScript などの Web サイト資産をダウンロードします。

JavaScript バンドル (フレームワーク コードを含む) は、サイズが大きいため、他のアセットのロードよりも時間がかかります。

  • そのため、ユーザーは画面に何かが表示されるまで待つ必要があります。

  • Web サイトのインデックス作成を担当する

    クローラーにも同じことが当てはまります。

  • LCPTTFB直帰率など、他の多くの SEO 指標が影響を受けます。

クライアントコンポーネント

クライアント コンポーネントは、単にユーザーのブラウザに送信されるコンポーネントです。

クライアント コンポーネント は、単なる裸の HTML コンポーネントや CSS コンポーネントではありません。これらが動作するには対話性が必要なので、サーバー上でレンダリングすることは実際には不可能です。

対話性は、反応 (useState、useEffect) などの JavaScript フレームワーク、ブラウザーのみ、または DOM API のいずれかによって保証されます。

クライアントコンポーネント宣言の前に、"use client" ディレクティブを置く必要があります。これにより、Nextjs はインタラクティブな部分 (useState、useEffect...) を無視し、ユーザーのブラウザに直接送信するように指示されます。

/client-component.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

さまざまな構成可能性の順列。

Nextjs で最もイライラするのは、サーバー コンポーネントクライアント コンポーネント の間のネストのルールを怠ると遭遇する可能性がある奇妙なバグです。

次のセクションでは、サーバー コンポーネントクライアント コンポーネント の間で考えられるさまざまなネストの並べ替えを紹介することで、そのことを明確にします。

これら 2 つの並べ替えは明らかに許可されているためスキップします: クライアント コンポーネントと別のクライアント コンポーネント、および別のサーバー コンポーネント内のサーバー コンポーネント。

サーバーコンポーネントをクライアントコンポーネントの子としてレンダリングする

クライアント コンポーネント をインポートし、サーバー コンポーネント内で通常どおりレンダリングできます。 ページレイアウト はデフォルトでサーバー コンポーネントであるため、この順列はある程度明白です。

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

サーバーコンポーネントをクライアントコンポーネントの子としてレンダリングする

クライアント コンポーネントをユーザーのブラウザに送信し、その内部にあるサーバー コンポーネントがデータをレンダリングしてフェッチするのを待つことを想像してください。サーバー コンポーネントはすでにクライアントに送信されているため、それは不可能です。その後、サーバー上でどのようにレンダリングできますか?

このタイプの順列が Nextjs でサポートされていないのはそのためです。

そのため、クライアント コンポーネント 内に サーバー コンポーネント をインポートして子としてレンダリングすることは避けてください。

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


jsx ツリー内のクライアント コンポーネントをプッシュダウンして、ユーザーのブラウザに送信される JavaScript を減らすように常に努めてください。

回避策 クライアント コンポーネント内でサーバー コンポーネントをレンダリングする

サーバーコンポーネントクライアントコンポーネントの子として直接インポートしてレンダリングすることはできませんが、react composabilityの性質.

その秘訣は、上位レベルのサーバー コンポーネント (

ParentServerComponent) で サーバー コンポーネントクライアント コンポーネント の子として渡すことです。

それを

パパトリック と呼びましょう :D.

このトリックにより、

クライアント コンポーネントをユーザーのブラウザに送信する前に、渡されたサーバー コンポーネントがサーバーでレンダリングされることが保証されます。

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



メモ アプリケーションの

/app/page.tsx ホーム ページで具体的な例を確認します。

ここでは、クライアント コンポーネント内の子として渡されるサーバー コンポーネントをレンダリングします。クライアント コンポーネントは、ブール状態変数の値に応じて、サーバー コンポーネントのレンダリングされたコンテンツを条件付きで表示または非表示にできます。

サーバーアクション

サーバー アクション は、クライアント側コンポーネントからサーバー上で宣言された関数を リモート かつ 安全に 呼び出すことを可能にする興味深い nextjs 機能です。 .

サーバー アクションを宣言するには、以下に示すように "use server" ディレクティブを関数の本体に追加するだけです。

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

"use server" ディレクティブは、関数にサーバー上でのみ実行されるサーバー側のコードが含まれていることを Nextjs に伝えます。

内部では、Nextjs は アクション ID を送信し、このアクション用に予約されたエンドポイントを作成します。

クライアント コンポーネント でこのアクションを呼び出すと、Nextjs は アクション ID で識別されるアクションの一意のエンドポイントに対して POST リクエストを実行し、 リクエストボディでアクションを呼び出すときに渡したシリアル化された引数。

この単純化した例でそれを明確にしましょう。

サーバー アクションを宣言するには、関数本体ディレクティブで "use server" を使用する必要があることを前に説明しました。しかし、一度に多数のサーバー アクションを宣言する必要がある場合はどうなるでしょうか。

以下のコードに示すように、ディレクティブをヘッダーまたはファイルの先頭で使用するだけです。

/server/actions.ts

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

サーバーアクションは常に非同期としてマークする必要があることに注意してください

  • そのため、上記のコードでは、createLogAction.

  • という名前のサーバー アクションを宣言しました。
  • このアクションは、サーバー上の /logs ディレクトリの下の特定のファイルにログ エントリを保存します。

  • ファイルの名前は、name アクション引数に基づいて付けられます。

  • アクション 作成日と message アクション引数で構成されるログ エントリを追加します。

ここで、CreateLogButton クライアント側コンポーネントで作成したアクションを使用してみましょう。

/components/CreateLogButton.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


ボタン コンポーネントは、アクションが実行されているかどうかを追跡するために使用される isSubmitting という名前のローカル状態変数を宣言しています。アクションが実行されると、ボタンのテキストが "Log Button" から "Loading..." に変わります。

サーバー アクションは、ログ ボタンコンポーネントをクリックすると呼び出されます。

ビジネスロジックのセットアップ

Note モデルの作成

まず、Note 検証スキーマとタイプを作成することから始めましょう。

モデルはデータ検証を処理することになっているため、その目的のために zod と呼ばれる一般的なライブラリを使用します。

zod の優れている点は、モデルの定義と対応する TypeScript の生成をシームレスなタスクにする、わかりやすい API です。

メモには派手で複雑なモデルは使用しません。各メモには一意の ID、タイトル、内容、作成日フィールドがあります。

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

InsertNoteSchema や WhereNoteSchema などの便利な追加スキーマも宣言しています。これにより、後でモデルを操作する再利用可能な関数を作成するときに作業が楽になります。

単純なインメモリデータベースの作成

メモはメモリに保存され、操作されます。

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

notes 定数がファイルにインポートされる (ページのリロードなど) たびに配列の状態が失われないように、notes 配列をグローバルのこのオブジェクトに保存しています。

アプリケーションのユースケースを作成する

メモの使用例を作成する

createNote ユースケースでは、ノート配列にノートを挿入できます。 notes.unshift メソッドは、要素を配列の末尾ではなく先頭にプッシュするため、notes.push メソッドの逆であると考えてください。

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


アップデートノートの使用例

updateNote を使用して、指定された ID のメモ配列内の特定のメモを更新します。まず要素のインデックスを検索し、見つからない場合はエラーをスローし、見つかったインデックスに基づいて対応するメモを返します。

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



メモの削除の使用例

deleteNote ユースケース関数は、指定されたノート ID を削除するために使用されます。
このメソッドは同様に動作します。まず、指定された ID でノートのインデックスを検索し、見つからない場合はエラーをスローして、見つかった ID によってインデックス付けされた対応するノートを返します。

"use client"
import { ServerComponent } from '@/components'

// Not allowed :(
export const ClientComponent = ()=>{

  return (
  <>

  <ServerComponent/>
  </>

  )
}


ノートの使用例を取得

getNote 関数は一目瞭然で、指定された ID のノートを検索するだけです。

import {ClientComponent} from '@/components/...'
import {ServerComponent} from '@/components/...'

export const ParentServerComponent = ()=>{

  return (
  <>
  <ClientComponent>
     <ServerComponent/>
  </ClientComponent>

</>
  )
}


メモを取得する使用例

メモ データベース全体をクライアント側にプッシュしたくないため、利用可能なメモの合計の一部のみを取得します。したがって、サーバー側のページネーションを実装する必要があります。

export const Component = ()=>{

  const serverActionFunction = async(params:any)=>{
    "use server"
    // server code lives here
    //...
    /

  }
  const handleClick = ()=>{
  await serverActionFunction()


  }

  return <button onClick={handleClick}>click me</button>
}

したがって、getNotes 関数を使用すると、基本的に page 引数を渡すことでサーバーから特定のページを取得できます。
limit 引数は、特定のページに存在する項目の数を決定するために使用されます。

例:
notes 配列に 100 個の要素が含まれており、limit 引数が 10 に等しい場合。

サーバーからページ 1 をリクエストすると、最初の 10 項目のみが返されます。

search 引数は、サーバー側の検索を実装するために使用されます。これは、タイトルまたはコンテンツ属性のいずれかに部分文字列として search 文字列を持つメモのみを返すようにサーバーに指示します。

メモの概要を取得する使用例

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

最近のアクティビティの取得の使用例

このユースケースは、ユーザーの最近のアクティビティに関する偽のデータを取得するために使用されます。

この関数は /dashboard ページで使用します。

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

最近のタグを取得する使用例

このユースケース関数は、メモ (#something) で使用されているさまざまなタグに関する統計を取得する役割を果たします。

この関数は /dashboard ページで使用します。

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


ユーザー情報の取得の使用例

このユースケース関数を使用して、名前やメールアドレスなどの一部のユーザー情報に関する偽のデータを返すだけです。

この関数は /dashboard ページで使用します。

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



ランダムなメモを取得する使用例

"use client"
import { ServerComponent } from '@/components'

// Not allowed :(
export const ClientComponent = ()=>{

  return (
  <>

  <ServerComponent/>
  </>

  )
}


App Router サーバーのアクションとキャッシュ

ホーム ページ (クライアント コンポーネント内のサーバー コンポーネントの回避策デモ)

このホームページでは、クライアント コンポーネント 内で サーバー コンポーネント をレンダリングするための以前のトリックまたは回避策をデモします (PaPa トリック :D) .

/app/page.tsx

import {ClientComponent} from '@/components/...'
import {ServerComponent} from '@/components/...'

export const ParentServerComponent = ()=>{

  return (
  <>
  <ClientComponent>
     <ServerComponent/>
  </ClientComponent>

</>
  )
}


上記のコードでは、アプリケーション内の "/" ページのレンダリングを担当する Home という 親サーバー コンポーネント を宣言しています。

RandomNote という名前の サーバー コンポーネントNoteOfTheDay という名前の クライアント コンポーネント をインポートしています。

RandomNote サーバー コンポーネントを子として NoteOfTheDay クライアント サイド コンポーネントに渡します。

/app/components/RandomNote.ts

export const Component = ()=>{

  const serverActionFunction = async(params:any)=>{
    "use server"
    // server code lives here
    //...
    /

  }
  const handleClick = ()=>{
  await serverActionFunction()


  }

  return <button onClick={handleClick}>click me</button>
}

RandomNote サーバー コンポーネントは次のように動作します:

  • getRandomNote ユースケース関数を使用してランダムなノートを取得します。

  • タイトルとメモ全体の内容の一部または部分文字列で構成されるメモの詳細をレンダリングします。

/app/components/NoteOfTheDay.ts

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

反対側の NoteOfTheDay クライアント コンポーネントは以下のように動作します。

  • 子プロップを入力として受け取り (この場合、RandomNote サーバー コンポーネントになります)、isVisible ブール状態変数値に応じて条件付きでレンダリングします。
  • このコンポーネントは、onClick イベント リスナーがアタッチされたボタンをレンダリングして、表示状態の値を切り替えます。

ノートページ

/app/notes/page.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

まず、以下を担当するサーバー コンポーネントである /app/notes/page.tsx ページを作成します。

  1. URL の末尾の ? マークの後に付加される文字列であるページ検索パラメーターを取得します: http://localhost:3000/notes?page=1&search=Something

  2. ローカルで宣言された関数 fetchNotes に検索パラメータを渡します。

  3. fetchNotes 関数は、以前に宣言したユースケース関数 getNotes を使用して、現在のノート ページを取得します。

  4. getNotes 関数を、"next/cache" からインポートされた unstable_cache というユーティリティ関数でラップしていることがわかります。不安定キャッシュ関数は、getNotes 関数からの応答をキャッシュするために使用されます。

データベースにメモが追加されていないことが確実な場合。ページがリロードされるたびにこれを押すのは意味がありません。したがって、unstable_cache 関数は、getNotes 関数の結果に "notes" タグを付けています。このタグは、後で "notes" メモが追加または削除された場合はキャッシュされます。

  1. fetchNotes 関数は、ノートと合計の 2 つの値を返します。

  2. 結果のデータ (メモと合計) は、メモのレンダリングを担当する

    NotesList と呼ばれる クライアント側コンポーネント に渡されます。

ユーザーが更新を押したとき。メモ データが取得されている間、空白のページがユーザーに表示されます。

この問題を解決するには、Nextjs という素晴らしい機能を利用します。
サーバーサイドページストリーミング.

これを行うには、

/app/notes/page.tsx ファイルの隣に loading.tsx ファイルを作成します。

/app/notes/loading.tsx


"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


ページがサーバーからストリーミングされている間、ユーザーにはページを読み込んでいるスケルトンが表示され、ユーザーはどのような種類のコンテンツが送信されるかを把握できます。

Next.js Deep Dive: Building a Notes App with Advanced Features

それはクールですね:)。 loading.tsx ファイルを作成するだけで完了です。あなたの ux は次のレベルまで成長しています。

/app/notes/components/NotesList.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

メモ リスト クライアント側コンポーネントサーバー コンポーネント (NotesPage) からメモとページネーション関連のデータを受け取ります。

次に、コンポーネントはノートの現在のページのレンダリングを処理します。個々のノート カードはすべて、NoteView コンポーネントを使用してレンダリングされます。

また、Next.js Link コンポーネントを使用して前後のページへのリンクも提供します。これは、シームレスで高速なクライアントを実現するために次のページと前のページのデータをプリフェッチするために不可欠です。 -サイドナビゲーション。

サーバーサイド検索を処理するために、useNotesSearchというカスタムフックを使用しています。これは基本的に、ユーザーが検索Input.

/app/notes/components/NoteView.ts


export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

NoteView コンポーネントは単純で、対応するタイトル、内容の一部、およびメモの詳細を表示または編集するためのアクション リンクを含む個々のメモ カードをレンダリングすることのみを担当します。

/app/notes/components/hooks/use-notes-search.ts


"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


useNotesSearch カスタム フックは次のように機能します:

  1. useState フックを使用して、initialSearch プロパティをローカル状態に保存します。

  2. useEffect React フックを使用して、currentPage または debouncedSearchValue 変数の値が変更されるたびにページ ナビゲーションをトリガーします。

  3. 新しいページの URL は、現在のページと検索値を考慮して構築されます。

  4. setSearch 関数は、ユーザーが検索入力に何かを入力するときに文字が変更されるたびに呼び出されます。これにより、短時間でナビゲーションが多すぎます。

  5. ユーザーが他の用語の入力をやめたときにのみナビゲーションをトリガーすることを避けるために、特定の時間 (この場合は 300 ミリ秒) の間、検索値をデバウンスしています。

メモの作成

次に、

CreateNoteForm クライアント コンポーネントのサーバー コンポーネント ラッパーである /app/notes/create/page.tsx を見てみましょう。

Next.js Deep Dive: Building a Notes App with Advanced Features

/app/notes/create/page.tsx


import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



/app/notes/create/components/CreateNoteForm.tsx


- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

CreateNoteForm クライアント コンポーネント フォームは、ユーザーからデータを取得し、それをローカル状態変数 (タイトル、コンテンツ) に保存する役割を果たします。

送信ボタンをクリックした後にフォームが送信されると、createNoteActiontitlecontentのローカル状態引数を使用して送信されます。 .

isSubmitting 状態ブール変数は、アクションの送信ステータスを追跡するために使用されます。

createNoteAction がエラーなく正常に送信された場合、ユーザーは /notes ページにリダイレクトされます。

/app/notes/create/actions/create-note.action.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

createNoteAction アクション コードは単純で、含まれるファイルの前に "use server" ディレクティブがあり、このアクションがクライアント コンポーネントで呼び出し可能であることを Next.js に示します。

サーバー アクションについて強調すべき点の 1 つは、アクション インターフェイスのみがクライアントに送信され、アクション自体内のコードは送信されないということです。

言い換えると、アクション内のコードはサーバー上に存在するため、クライアントからサーバーに送られる入力を信頼すべきではありません。

そのため、ここでは zod を使用して、以前に作成したスキーマを使用して rawNote アクション引数を検証しています。

入力を検証した後、検証されたデータを使用して createNote ユースケースを呼び出します。

ノートが正常に作成されると、revalidateTag 関数が呼び出され、"notes" としてタグ付けされたキャッシュ エントリが無効になります (unstable_cache 関数を思い出してください)これは /notes ページで使用されます)。

ノートの詳細ページ

ノートの詳細ページには、一意の ID が与えられた特定のノートのタイトルと完全な内容が表示されます。それに加えて、メモを編集または削除するためのいくつかのアクション ボタンが表示されます。

Next.js Deep Dive: Building a Notes App with Advanced Features

/app/notes/[noteId]/page.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


  1. まず、ページ プロパティからページ パラメータを取得します。 Next.js 13 では、params ページ引数は Promise なので待機する必要があります。

  2. それを行った後、params.noteId をローカルで宣言された関数 fetchNote に渡します。

/app/notes/[noteId]/fetchers/fetch-note.ts

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

  1. fetchNote 関数は、getNote ユースケースを unstable_cache でラップし、返された結果を "note-details" および note-details/${id} タグ。

  2. "note-details" タグを使用すると、すべてのメモ詳細キャッシュ エントリを一度に無効にすることができます。

  3. 一方、

    note-details/${id} タグは、一意の ID で定義された特定のメモにのみ関連付けられます。したがって、これを使用して、ノートのセット全体ではなく、特定のノートのキャッシュ エントリを無効にすることができます。

/app/notes/[noteId]/loading.tsx


export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

リマインダー

loading.tsx は、ノートの詳細ページがサーバーでデータを取得している間にレンダリングされる特別な Next.js ページです。

言い換えると、

fetchNote 関数の実行中に、空白の画面の代わりにスケルトン ページがユーザーに表示されます。

この nextjs 機能は

ページ ストリーミング と呼ばれます。これにより、コンテンツを段階的にストリーミングしながら、動的ページの静的な親レイアウト全体を送信できます。

これにより、ページの動的コンテンツがサーバー上で取得されている間に UI がブロックされることが回避され、パフォーマンスとユーザー エクスペリエンスが向上します。

/app/notes/[noteId]/components/DeleteNoteButton.tsx


"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


次に、

DeleteNoteButton クライアント側コンポーネントを見てみましょう。

Next.js Deep Dive: Building a Notes App with Advanced Features

このコンポーネントは、削除ボタンをレンダリングし、

deleteNoteAction を実行し、アクションが正常に実行されるとユーザーを /notes ページにリダイレクトします。

アクションの実行ステータスを追跡するために、ローカル状態変数

isDeleting を使用しています。

/app/notes/[noteId]/actions/delete-note.action.tsx


import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



deleteNoteAction コードは次のように機能します:

    アクション入力の解析と検証に zod を使用しています。
  1. 入力が安全であることを確認した後、それを
  2. deleteNote ユースケース関数に渡します。
  3. アクションが正常に実行されると、
  4. revalidateTag を使用して、"notes"note-details/${where.id} キャッシュの両方を無効にします。エントリ。
メモページの編集

Next.js Deep Dive: Building a Notes App with Advanced Features

/app/notes/[noteId]/edit/page.tsx


- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

/app/notes/[noteId]/edit/page.tsx ページは、params Promise から noteId パラメータを取得するサーバー コンポーネントです。

次に、fetchNote 関数を使用してノートを取得します。

フェッチが成功した後。メモを EditNoteForm クライアント側コンポーネントに渡します。

/app/notes/[noteId]/edit/components/EditNoteForm.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

EditNoteForm クライアント側コンポーネントはメモを受け取り、ユーザーがメモの詳細を更新できるフォームをレンダリングします。

title および content ローカル状態変数は、対応する入力またはテキストエリアの値を保存するために使用されます。

メモを更新 ボタンを介してフォームが送信されたとき。 updateNoteAction は、titlecontent の値を引数として使用して呼び出されます。

isSubmitting 状態変数は、アクションの送信ステータスを追跡するために使用され、アクションの実行時に読み込みインジケーターを表示できます。

/app/notes/[noteId]/edit/actions/edit-note.action.ts

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


updateNoteAction アクションは次のように機能します:

  1. アクション入力は、対応する zod スキーマ (WhereNoteSchema および InsertNoteSchema) を使用して検証されます。
  2. その後、解析および検証されたデータを使用して updateNote ユースケース関数が呼び出されます。
  3. ノートが正常に更新された後、"notes" タグと note-details/${where.id} タグを再検証します。

ダッシュボード ページ (コンポーネント レベルのストリーミング機能)

/app/dashboard/page.tsx

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



/app/dashboard/page.tsx ページは、NotessummaryRecentActivity、および TagCloud.

各サーバー コンポーネントは独自のデータを個別に取得します。

各サーバー コンポーネントは React

Suspense 境界でラップされます。

サスペンス境界の役割は、子サーバー コンポーネントが独自のデータをフェッチしているときに、フォールバック コンポーネント (この場合は

スケルトン) を表示することです。

別の言い方をすると、

サスペンス 境界により、何らかの条件が満たされる (子の内部のデータがロードされる) まで、子のレンダリングを延期または遅らせることができます。

そのため、ユーザーはページを多数のスケルトンの組み合わせとして見ることができます。すべての個々のコンポーネントの応答がサーバーによってストリーミングされている間。

このアプローチの主な利点の 1 つは、1 つ以上のサーバー コンポーネントが他のコンポーネントに比べて時間がかかる場合に、UI のブロックを回避できることです。

各コンポーネントの個々のフェッチ時間が次のように分布すると仮定します。

  1. Notessummary の読み込みには 2 秒かかります。
  2. RecentActivity の読み込みには 1 秒かかります。
  3. TagCloud の読み込みには 3 秒かかります。

更新をクリックすると、最初に 3 つのスケルトン ローダーが表示されます。

1 秒後に RecentActivity コンポーネントが表示されます。
2 秒後、Notessummary が続き、次に TagCloud が続きます。

つまり、コンテンツを表示する前にユーザーを 3 秒待たせるのではなく。 RecentActivity を最初に表示することで、時間を 2 秒短縮しました。

このインクリメンタル レンダリング アプローチにより、ユーザー エクスペリエンスとパフォーマンスが向上します。

Next.js Deep Dive: Building a Notes App with Advanced Features

個々の サーバー コンポーネント のコードが以下で強調表示されています。

/app/dashboard/components/RecentActivity.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

RecentActivity サーバー コンポーネントは、基本的に getRecentActivity ユースケース関数を使用して最後のアクティビティを取得し、順序なしリストでレンダリングします。

/app/dashboard/components/TagCloud.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

TagCloud サーバー側コンポーネントは、ノートのコンテンツで使用されているすべてのタグ名をそれぞれの数とともに取得してレンダリングします。

/app/dashboard/components/Notessummary.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


Notessummary サーバー コンポーネントは、getNotesummary ユースケース関数を使用して概要情報を取得した後、それをレンダリングします。

プロフィールページ (並行ルート機能)

次に、プロフィール ページに進み、Parallel Routes と呼ばれる興味深い nextjs 機能を説明します。

並列ルートにより、同時にまたは条件付き同じレイアウト内で1つ以上のページをレンダリングできます。

以下の例では、ユーザー情報ページユーザーノートページ/app/profileという同じレイアウト内にレンダリングします。 .

名前付きスロットを使用して、並列ルートを作成できます。名前付きスロットはサブ ページとして正確に宣言されますが、通常のページとは異なり、フォルダー名の前に @ 記号を付ける必要があります。

たとえば、/app/profile/ フォルダー内に 2 つの名前付きスロットを作成します。

  1. /app/profile/@info ユーザー情報ページ。
  2. ユーザーノートページの/app/profile/@notes

Next.js Deep Dive: Building a Notes App with Advanced Features

次に、/profile ページのレイアウトを定義するレイアウト ファイル /app/profile/layout.tsx ファイルを作成しましょう。

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

上記のコードからわかるように、@info ページと @notes ページ内のコンテンツを含む info パラメータと notes パラメータにアクセスできるようになりました。

したがって、@info ページは左側にレンダリングされ、@notes は右側にレンダリングされます。

page.tsx のコンテンツ (children によって参照される) は、ページの下部にレンダリングされます。

@infoページ

/app/profile/@info/page.tsx

export const ServerComponent = async ()=>{
  const posts = await getSomeData()
  // call any nodejs api or server function during the component rendering

  // Don't even think about it. No useEffects are allowed here x_x

  const pasta = await getPasta()

  return (
  <ul>
  {
      data.map(d=>(
      <li>{d.title}</li>

      ))

    }
  </ul>
  )

}

UserInfoPage は、getUserInfo ユースケース関数を使用してユーザー情報を取得するサーバー コンポーネントです。

上記のフォールバック スケルトンは、コンポーネントがデータを取得してサーバー上でレンダリングされるときにユーザーのブラウザーに送信されます (サーバー サイド ストリーミング)。

/app/profile/@info/loading.tsx

"use client"
import React,{useEffect,useState} from "react"

export const ClientComponent = ()=>{
  const [value,setValue] = useState()

  useEffect(()=>{
    alert("Component have mounted!")

    return ()=>{
      alert("Component is unmounted")
    }
  },[])
  //..........
  return (
  <>
  <button onClick={()=>alert("Hello, from browser")}></button>
{/* .......... JSX Code ...............*/}
</>
  )
}


@noteページ

同じことが LastNotesPage サーバー側コンポーネントにも当てはまります。スケルトン UI がユーザーに表示されている間に、データをフェッチしてサーバー上でレンダリングします

/app/profile/@notes/page.tsx

import { ClientComponent } from '@/components'

// Allowed :)
export const ServerComponent = ()=>{

  return (
  <>

  <ClientComponent/>
  </>

  )
}



/app/profile/@notes/loading.tsx

"use client"
import { ServerComponent } from '@/components'

// Not allowed :(
export const ClientComponent = ()=>{

  return (
  <>

  <ServerComponent/>
  </>

  )
}


エラーページ

次に、Nextjs の非常に優れた機能である error.tsx ページを見てみましょう。

Next.js Deep Dive: Building a Notes App with Advanced Features

アプリケーションを運用環境にデプロイするとき、ページの 1 つから捕捉されなかったエラーがスローされたときに、ユーザー フレンドリーなエラーを表示したいと思うでしょう。

ここで、error.tsx ファイルが登場します。

まず、数秒後にキャッチされないエラーをスローするサンプル ページを作成しましょう。

/app/error-page/page.tsx

import {ClientComponent} from '@/components/...'
import {ServerComponent} from '@/components/...'

export const ParentServerComponent = ()=>{

  return (
  <>
  <ClientComponent>
     <ServerComponent/>
  </ClientComponent>

</>
  )
}


ページがスリープしているとき、またはスリープ関数の実行を待機しているとき。以下の読み込みページがユーザーに表示されます。

/app/error-page/loading.tsx

export const Component = ()=>{

  const serverActionFunction = async(params:any)=>{
    "use server"
    // server code lives here
    //...
    /

  }
  const handleClick = ()=>{
  await serverActionFunction()


  }

  return <button onClick={handleClick}>click me</button>
}

数秒後にエラーがスローされ、ページが削除されます:(.

これを避けるために、/app/error-page/page の エラー境界 として機能するコンポーネントをエクスポートする error.tsx ファイルを作成します。 .tsx.

/app/error-page/error.tsx

- app/
  - notes/ --------------------------------> Server Side Caching Features
    - components/
      - NotesList.tsx
    - [noteId]/
      - actions/ -------------------------> Server Actions feature
        - delete-note.action.ts
        - edit-note.action.ts
      - components/
        - DeleteButton.tsx
      - page.tsx
      - edit/
        - components/
          - EditNoteForm.tsx
        - page.tsx
        - loading.tsx --------------------> Page level Streaming feature
    - create/
      - actions/
        - create-note.action.ts
      - components/
        - CreateNoteForm.tsx
      - page.tsx
  - error-page/
    - page.tsx
    - error.tsx --------------------------> Error Boundary as a page feature
  - dashboard/ ---------------------------> Component Level Streaming Feature
    - components/
      - NoteActivity.tsx
      - TagCloud.tsx
      - NotesSummary.tsx
    - page.tsx
  - profile/ ----------------------------->[6] Parallel Routes Feature
    - layout.tsx
    - page.tsx
    - @info/
      - page.tsx
      - loading.tsx
    - @notes/
      - page.tsx
      - loading.tsx
- core/ --------------------------> Our business logic lives here
  - entities/
    - note.ts
  - use-cases/
    - create-note.use-case.ts
    - update-note.use-case.ts
    - delete-note.use-case.ts
    - get-note.use-case.ts
    - get-notes.use-case.ts
    - get-notes-summary.use-case.ts
    - get-recent-activity.use-case.ts
    - get-recent-tags.use-case.ts

結論

このガイドでは、実用的なメモ アプリケーションを構築することによって、Next.js の主要な機能を検討しました。以下について説明しました:

  1. サーバー コンポーネントとクライアント コンポーネントを備えた App Router
  2. ロードとエラー処理
  3. サーバーアクション
  4. データのフェッチとキャッシュ
  5. ストリーミングとサスペンス
  6. 並行ルート
  7. エラー境界

これらの概念を現実世界のプロジェクトに適用することで、私たちは Next.js の強力な機能を実際に体験することができました。理解を確実にするための最良の方法は実践することであることを忘れないでください。

次のステップ

  • 完全なコードを確認してください: github.com/spithacode/next-js-features-notes-app
  • 独自の機能でアプリケーションを拡張します
  • Next.js の公式ドキュメントを常に最新の状態に保ってください

ご質問がある場合、またはさらに話し合いたい場合は、お気軽にここからご連絡ください。

コーディングを楽しんでください!

以上がNext.js の詳細: 高度な機能を備えた Notes アプリの構築の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。