>웹 프론트엔드 >JS 튜토리얼 >Next.js 심층 분석: 고급 기능을 갖춘 Notes 앱 구축

Next.js 심층 분석: 고급 기능을 갖춘 Notes 앱 구축

DDD
DDD원래의
2024-11-03 15:07:031079검색

Next.js Deep Dive: Building a Notes App with Advanced Features## 소개 및 목표

이 블로그 기사에서는 실제 시나리오에 필요한 가장 중요한 Next.js 기능을 살펴보고 싶습니다.

저는 저 자신과 관심 있는 독자를 위한 단일 참고 자료로 이 블로그 기사를 작성했습니다. 전체 nextjs 문서를 검토할 필요가 없습니다. 정기적으로 방문하여 지식을 되새길 수 있는 다음의 중요한 실용적 기능이 모두 포함된 축약된 블로그 기사를 갖는 것이 더 쉬울 것이라고 생각합니다!

노트 애플리케이션을 병렬로 구축하면서 다음 기능을 함께 살펴보겠습니다.

  • 앱 라우터

    • 서버 구성요소
    • 클라이언트 구성요소
    • 중첩 라우팅
    • 동적 경로
  • 로드 및 오류 처리

  • 서버 작업

    • 서버 액션 생성 및 사용
    • 클라이언트 구성 요소와 서버 작업 통합
  • 데이터 가져오기 및 캐싱

    • 서버 측 캐싱에 불안정_cache 사용 1. revalidateTag를 사용하여 캐시 재검증
    • 서버 측 캐싱을 위해 stable_cache 사용
  • 스트리밍과 서스펜스

    • loading.tsx를 사용한 페이지 수준 스트리밍
    • Suspense를 사용한 구성 요소 수준 스트리밍
  • 평행 노선

    • 이름이 지정된 슬롯 생성 및 사용
    • 여러 페이지 구성 요소의 동시 렌더링 구현
  • 오류 처리

    • 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 저장소 spithacode에서 찾을 수 있는 최종 코드로 바로 이동하세요.

더 이상 고민하지 말고 시작해 보세요!

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

주요 개념

Notes 애플리케이션 개발을 시작하기 전에 앞으로 진행하기 전에 알아야 할 몇 가지 주요 nextjs 개념을 소개하고 싶습니다.

앱 라우터

App Router는 기존 "/page" 디렉토리에서는 불가능했던 많은 기능을 지원하는 새로운 디렉토리 "/app"입니다. :

  1. 서버 구성요소.
  2. 공유 레이아웃:layout.tsx 파일.
  3. 중첩 라우팅: 폴더를 서로 중첩할 수 있습니다. 페이지 경로 URL은 동일한 폴더 중첩을 따릅니다. 예를 들어, [noteId] 동적 매개변수가 "1""/notes/1/edit입니다. 페이지가 사용자 브라우저로 스트리밍될 때 렌더링되는 구성 요소를 내보내는

  4. /loading.tsx 파일입니다.
  5. /error.tsx 페이지에서 확인되지 않은 오류가 발생할 때 렌더링되는 구성 요소를 내보내는 파일입니다.
  6. Notes 애플리케이션을 구축하는 동안 겪게 될 병렬 라우팅 및 많은 기능.
  7. 서버 구성 요소와 클라이언트 구성 요소

Nextjs

/app

Router를 터치하기 전에 모두가 숙지해야 할 정말 중요한 주제에 대해 살펴보겠습니다. 서버 구성요소

서버 구성 요소

는 기본적으로 서버에서 렌더링되는 구성 요소입니다.

"클라이언트 사용"

지시문이 앞에 오지 않는 모든 구성 요소는 기본적으로 페이지와 레이아웃을 포함한 서버 구성 요소입니다. 서버 구성 요소는 모든 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를 포함한 웹사이트 자산을 다운로드합니다.

프레임워크 코드가 포함된 자바스크립트 번들은 크기 때문에 나머지 자산보다 로드하는 데 더 많은 시간이 걸립니다.

    따라서 사용자는 화면에 표시되는 내용을 보려면 기다려야 합니다.
  • 웹사이트 색인 생성을 담당하는
  • 크롤러

    에도 동일한 사항이 적용됩니다.

  • LCP

    , TTFB, 이탈률 등 다양한 SEO 지표가 영향을 받습니다.

  • 클라이언트 구성요소

클라이언트 구성 요소

는 단순히 사용자의 브라우저에 제공되는 구성 요소입니다.

클라이언트 구성 요소

는 단순한 HTML 및 CSS 구성 요소가 아닙니다. 작동하려면 상호작용이 필요하므로 서버에서 렌더링하는 것은 실제로 불가능합니다.

상호작용

은 반응( useState, useEffect)과 같은 자바스크립트 프레임워크나 브라우저만 또는 DOM API를 통해 보장됩니다.

클라이언트 구성 요소 선언 앞에는 "use client" 지시문이 와야 합니다. 이는 Nextjs가 대화형 부분(useState,useEffect...)을 무시하고 사용자 브라우저로 바로 전달하도록 지시합니다.

/client-comComponent.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에서 가장 실망스러운 점은 서버 구성 요소클라이언트 구성 요소 사이의 중첩 규칙을 놓친 경우 발생할 수 있는 이상한 버그입니다.

따라서 다음 섹션에서는 서버 구성 요소클라이언트 구성 요소

사이에 가능한 다양한 중첩 순열을 보여줌으로써 이를 명확히 할 것입니다.

클라이언트 구성 요소, 다른 클라이언트 구성 요소 및 다른 서버 구성 요소 내부의 서버 구성 요소는 분명히 허용되므로 이 두 가지 순열은 건너뛰겠습니다.

서버 구성 요소를 클라이언트 구성 요소의 하위 구성 요소로 렌더링

클라이언트 구성 요소를 가져와 서버 구성 요소 내에서 정상적으로 렌더링할 수 있습니다. 페이지레이아웃이 기본 서버 구성요소
이기 때문에 이러한 순열은 다소 분명합니다.

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 트리에서 클라이언트 구성 요소를 아래로 눌러 사용자의 브라우저로 전송되는 자바스크립트를 줄이도록 노력하세요.

해결 방법 클라이언트 구성 요소 내부의 렌더링 서버 구성 요소

서버 구성 요소클라이언트 구성 요소의 하위 요소로 직접 가져오고 렌더링하는 것은 불가능하지만 반응 결합성 특성.

비결은

서버 구성 요소를 더 높은 수준의 서버 구성 요소( 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는 Action Id를 전달하고 이 액션에 대해 예약된 엔드포인트를 생성합니다.

따라서 클라이언트 구성 요소에서 이 액션을 호출하면 Nextjs는 액션 ID로 식별되는 액션 고유 엔드포인트에 대해 POST 요청을 수행합니다. 요청 본문.

에서 작업을 호출할 때 전달한 직렬화된 인수입니다.

이 간단한 예를 통해 이를 더 명확하게 설명하겠습니다.

이전에 서버 작업을 선언하려면 함수 본문 지시문에서 "서버 사용"을 사용해야 한다는 것을 살펴봤습니다. 하지만 한 번에 여러 서버 액션을 선언해야 한다면 어떨까요?

아래 코드에 표시된 대로 헤더나 파일 시작 부분에 지시어를 사용하면 됩니다.

/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 클라이언트 측 구성 요소에서 생성된 작업을 활용해 보겠습니다.

/comComponents/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이라는 로컬 상태 변수를 선언합니다. 작업이 실행되면 버튼 텍스트가 "로그 버튼"에서 "로드 중..."

으로 변경됩니다.

서버 액션로그 버튼 구성 요소를 클릭하면 호출됩니다.

비즈니스 로직 설정

노트 모델 만들기

우선 노트 유효성 검사 스키마와 유형을 만드는 것부터 시작해 보겠습니다.

모델은 데이터 검증을 처리해야 하므로 해당 목적에 널리 사용되는 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 상수를 파일로 가져올 때마다(페이지 새로 고침...) 배열 상태가 손실되는 것을 방지하기 위해 전역 this 개체에 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가 ​​지정된 Notes 배열의 특정 메모를 업데이트합니다. 먼저 요소의 인덱스를 찾고, 찾을 수 없으면 오류가 발생하며, 발견된 인덱스를 기반으로 해당 메모를 반환합니다.

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 인수는 서버 측 검색을 구현하는 데 사용됩니다. 제목이나 콘텐츠 속성에 검색 문자열이 하위 문자열로 포함된 노트만 반환하도록 서버에 지시합니다.

메모 요약 사용 사례 가져오기

- 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/>
  </>

  )
}


앱 라우터 서버 작업 및 캐싱

홈 페이지(클라이언트 구성 요소 내부의 서버 구성 요소 해결 방법 데모)

이 홈 페이지에서는 클라이언트 구성 요소 내부에서 서버 구성 요소를 렌더링하기 위한 이전 트릭이나 해결 방법을 시연합니다(PaPa 트릭:D). .

/app/page.tsx

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

export const ParentServerComponent = ()=>{

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

</>
  )
}


위 코드에서는 애플리케이션의 "/" 페이지 렌더링을 담당하는 Home이라는 상위 서버 구성 요소를 선언합니다.

RandomNote라는 Server ComponentNoteOfTheDay라는 ClientComponent를 가져오고 있습니다.

RandomNote 서버 구성 요소를 하위 구성 요소로 NoteOfTheDay 클라이언트 측 구성 요소

에 전달하고 있습니다.

/app/comComponents/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/comComponents/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 클라이언트 구성 요소는 아래 설명과 같이 작동합니다.

  • children prop을 입력으로 사용하고(우리의 경우 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. "next/cache"에서 가져온 unstable_cache라는 유틸리티 함수로 getNotes 함수를 래핑하고 있음을 알 수 있습니다. 불안정한 캐시 기능은 getNotes 기능의 응답을 캐시하는 데 사용됩니다.

데이터베이스에 메모가 추가되지 않았다고 확신하는 경우. 페이지가 다시 로드될 때마다 이를 누르는 것은 의미가 없습니다. 따라서 unstable_cache 함수는 나중에 "notes""notes" 태그를 사용하여 getNotes 함수 결과에 태그를 지정합니다. > 메모가 추가되거나 삭제되면 캐시됩니다.

  1. fetchNotes 함수는 메모와 합계라는 두 가지 값을 반환합니다.

  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/comComponents/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

Notes 목록 클라이언트 측 구성 요소 NotesPage인 상위 서버 구성 요소로부터 메모 및 페이지 매김 관련 데이터를 받습니다.

그런 다음 구성 요소는 현재 노트 페이지 렌더링을 처리합니다. 모든 개별 메모 카드는 NoteView 구성 요소

를 사용하여 렌더링됩니다.

또한 원활하고 빠른 클라이언트를 확보하기 위해 다음 및 이전 페이지 데이터를 미리 가져오는 데 필수적인 Next.js Link 구성 요소를 사용하여 이전 및 다음 페이지에 대한 링크를 제공합니다. -사이드 네비게이션.

서버 측 검색을 처리하기 위해 우리는 기본적으로 사용자가 검색 입력useNotesSearch라는 사용자 정의 후크를 사용하고 있습니다. 🎜>.

/app/notes/comComponents/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/comComponents/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 prop을 로컬 상태에 저장합니다.

  2. 우리는

    currentPage 또는 debouncedSearchValue 변수 값이 변경될 때마다 페이지 탐색을 트리거하기 위해 useEffect React 후크를 사용하고 있습니다.

  3. 새 페이지 URL은 현재 페이지와 검색값을 고려하여 구성됩니다.

  4. setSearch 함수는 사용자가 검색 입력에 무언가를 입력할 때 문자가 바뀔 때마다 호출됩니다. 그러면 짧은 시간에 너무 많은 탐색이 발생하게 됩니다.

  5. 사용자가 다른 용어 입력을 멈출 때마다 탐색만 실행되는 것을 방지하기 위해 특정 시간(우리의 경우 300ms) 동안 검색 값을 디바운싱합니다.

메모 작성

다음으로

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/comComponents/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 작업 코드는 간단합니다. 포함 파일 앞에는 이 작업이 클라이언트 구성 요소에서 호출 가능함을 Next.js에 나타내는 "use server" 지시문이 옵니다.

서버 액션에 대해 강조해야 할 한 가지 점은 액션 인터페이스만 클라이언트에 제공되고 액션 자체 내부의 코드는 제공되지 않는다는 것입니다.

즉, 액션 내부의 코드는 서버에 상주하므로 클라이언트에서 서버로 들어오는 입력을 신뢰해서는 안 됩니다.

이것이 바로 이전에 생성된 스키마를 사용하여 rawNote 작업 인수를 검증하기 위해 여기에서 zod를 사용하는 이유입니다.

입력 내용을 검증한 후 검증된 데이터로 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 페이지 인수가 약속이기 때문에 기다려야 합니다.

  2. 그런 다음 params.noteIdfetchNote 로컬로 선언된 함수에 전달합니다.

/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]/comComponents/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/comComponents/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 클라이언트 측 구성 요소는 메모를 수신하고 사용자가 메모의 세부 정보를 업데이트할 수 있는 양식을 렌더링합니다.

titlecontent 로컬 상태 변수는 해당 입력 또는 텍스트 영역 값을 저장하는 데 사용됩니다.

메모 업데이트 버튼을 통해 양식이 제출된 경우. updateNoteActiontitlecontent 값을 인수로 사용하여 호출됩니다.

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 스키마(WhereNoteSchemaInsertNoteSchema)를 사용하여 검증됩니다.
  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 페이지는 더 작은 서버 측 구성 요소로 분류됩니다: NotesSummary, RecentActivityTagCloud.

각 서버 구성요소는 자체 데이터를 독립적으로 가져옵니다.

각 서버 구성 요소는 React Suspense 경계로 둘러싸여 있습니다.

서스펜스 경계의 역할은 하위 서버 구성 요소가 자체 데이터를 가져올 때 대체 구성 요소(우리의 경우 스켈레톤)를 표시하는 것입니다.

또는 Suspense 경계를 사용하면 특정 조건이 충족될 때까지 해당 하위 항목의 렌더링을 연기하거나 지연할 수 있습니다( 하위 항목 내부의 데이터가 로드되는 중).

그래서 사용자는 페이지를 여러 뼈대의 조합으로 볼 수 있게 됩니다. 모든 개별 구성 요소에 대한 응답이 서버에 의해 스트리밍되는 동안

이 접근 방식의 주요 장점 중 하나는 하나 이상의 서버 구성 요소가 다른 구성 요소에 비해 시간이 더 오래 걸리는 경우 UI를 차단하지 않는다는 것입니다.

따라서 각 구성요소의 개별 가져오기 시간이 다음과 같이 분포된다고 가정하면:

  1. NotesSummary 로드하는 데 2초가 걸립니다.
  2. RecentActivity를 로드하는 데 1초가 걸립니다.
  3. TagCloud 로딩 시간은 3초 정도 소요됩니다.

새로고침을 누르면 가장 먼저 보게 될 것은 3개의 스켈레톤 로더입니다.

1초 후에 RecentActivity 구성요소가 표시됩니다.
2초 후에 NotesSummaryTagCloud로 이어집니다.

따라서 사용자가 콘텐츠를 보기 전에 3초 동안 기다리게 만드는 대신. 최근 활동을 먼저 보여줌으로써 그 시간을 2초 단축시켰습니다.

이 증분 렌더링 접근 방식은 더 나은 사용자 경험과 성능을 제공합니다.

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

개별 서버 구성요소에 대한 코드가 아래에 강조표시되어 있습니다.

/app/dashboard/comComponents/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/comComponents/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/comComponents/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 사용 사례 함수를 사용하여 요약 정보를 가져온 후 렌더링합니다.

프로필 페이지(병렬 경로 기능)

이제 평행 경로라는 흥미로운 nextjs 기능을 살펴볼 프로필 페이지로 이동하겠습니다.

병렬 경로를 사용하면 동시에 또는 조건부로동일한 레이아웃 내에서 하나 이상의 페이지를 렌더링할 수 있습니다.

아래 예에서는 /app/profile과 동일한 레이아웃 내에서 사용자 정보 페이지사용자 메모 페이지를 렌더링합니다. .

이름이 지정된 슬롯을 사용하여 병렬 경로를 생성할 수 있습니다. 명명된 슬롯은 정확히 하위 페이지로 선언되지만 일반 페이지와 달리 @ 기호가 폴더 이름 앞에 와야 합니다.

예를 들어 /app/profile/ 폴더 내에서 두 개의 이름이 지정된 슬롯을 생성합니다.

  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 페이지 내부의 콘텐츠가 포함된 infonotes 매개변수에 액세스할 수 있습니다.

그래서 @info 페이지는 왼쪽에 렌더링되고 @notes 페이지는 오른쪽에 렌더링됩니다.

page.tsx의 콘텐츠(children에서 참조)는 페이지 하단에 렌더링됩니다.

@정보 페이지

/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>
  )

}

UserInfoPagegetUserInfo 사용 사례 기능을 사용하여 사용자 정보를 가져오는 서버 구성 요소입니다.

위 대체 스켈레톤은 구성 요소가 데이터를 가져오고 서버에서 렌더링될 때(서버 측 스트리밍) 사용자 브라우저로 전송됩니다.

/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 ...............*/}
</>
  )
}


@notes 페이지

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/>
  </>

  )
}


오류 페이지

이제 error.tsx 페이지에서 Nextjs의 아주 좋은 기능을 살펴보겠습니다.

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

애플리케이션을 프로덕션 환경에 배포할 때 페이지 중 하나에서 발견되지 않은 오류가 발생할 때 사용자에게 친숙한 오류를 표시하고 싶을 것입니다.

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. 서버 및 클라이언트 구성요소가 포함된 앱 라우터
  2. 로드 및 오류 처리
  3. 서버 작업
  4. 데이터 가져오기 및 캐싱
  5. 스트리밍과 서스펜스
  6. 평행 노선
  7. 오류 경계

실제 프로젝트에 이러한 개념을 적용함으로써 우리는 Next.js의 강력한 기능에 대한 실무 경험을 얻었습니다. 이해를 다지는 가장 좋은 방법은 연습임을 기억하세요.

다음 단계

  • 전체 코드 살펴보기: github.com/spithacode/next-js-features-notes-app
  • 자신만의 기능으로 애플리케이션 확장
  • 공식 Next.js 문서로 최신 소식을 받아보세요

질문이 있으시거나 추가 논의를 원하시면 여기로 연락주세요.

즐거운 코딩하세요!

위 내용은 Next.js 심층 분석: 고급 기능을 갖춘 Notes 앱 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.