반응형
옛날에 위의 영상을 보고 공부했던 내용을 개인 필기용으로 적어둔 것입니다
File System Routing
/app/about example.com/about
- Dynamic Route
/app/[slug]example.com/{slug}- Catch-All Route
/app/[...id]example.com/…id/…id/…id
- Catch-All Route
- ()
/app/(group)- will be ignored by routing system
Reserved Filenames
page.tsx(in TS) orpage.js
실제 UI를 정의하는 기본 React 컴포넌트를 exportlayout.tsx(in TS) orlayout.js
UI that surrounds the entire applicationroute.tsx(in TS) orroute.tsx- Route Handler- JSON 등을 return하는 데에 사용할 수 있음, page와 같은 디렉토리에 사용할 수 없음
Route Handler
- HTTP 메소드들(GET, POST, PUT...)와 같은 이름을 가지는 함수를 1개 이상 export 할 수 있음
- 각 함수는 들어오는 request에 대한 정보를 제공하는 request parameter를 가짐
- 해당 request를 처리하기 위해 함수에서 response를 return할 수 있음
- 예시: POST로 form을 받아서 뭔가 한 뒤 Response Body 'we did it'을 반환
- Route Handler들은 언제나 서버사이드에서 실행되며 기본적으로 Node.js 런타임에서 실행
export const runtime으로 변경 가능
- Route Handler들은 언제나 서버사이드에서 실행되며 기본적으로 Node.js 런타임에서 실행
export async function POST(request: Request) {
const data = await request.json();
//do-something
return new Response('we did it');
}
Request & Response API
- 있으면 삶이 편해지는 기능들을 제공
- 예를 들어 Response로 JSON을 보내고 싶다든가 할 때에 유용하다
import { NextRequest, NextResponse } from 'next/server';
export async function PATCH(request: NextRequest) {
const url = request.nextUrl;
return NextResponse.json({message: "asdf"});
}
Layouts
- 기본적으로
/app/layout.tsx가 있어서, 전 애플리케이션에 있어서의 outer UI 루트 레이아웃을 정의함 - 레이아웃은 페이지와 비슷하지만, 말하자면 자식들에게 상속되는 레이아웃임
export default function RootLayout({ children, }: { children: React.ReactNode }) { return (<html> <body> {children} </body> </html> ); } - 레이아웃은 Nest될 수 있음
- 레이아웃에서도 fetch()로 FETCHING 이 가능
- layout group (괄호가 들어간 폴더)와 결합하여 쓸 수도 있음?
- 레이아웃의 UI와 상태는 route 변화되더라도 계속 유지됨
- 예를 들어, 레이아웃의 return값에
<NavMenu/>가 포함되어 있다면, 해당 레이아웃이 사용되는 모든 페이지에서<NavMenu/>는 재렌더링되지않음 - 레이아웃 컴포넌트를 매 내비게이션 때마다 reinitalise하려면
template.tsx를 이용할 수 있음- which re-mounts on route change
- 예를 들어, 레이아웃의 return값에
Server Component
Handle Server-Side Rendering for SEO
- 전통적으로, Next.js는 SSR, ISR, SSG와 같은 다양한 종류의 렌더링 기법들을 대응해왔음
- 하지만 Next.js 13에서는, 모든 페이지는 Server Component이다
- 서버에서 렌더링된다 (클라에게 HTML을 쏜다)
- 따라서, React의
useEffect()와 같은 Client-Side 코드들을 그대로 쓸 수 없다
- 최상단에
'use client';로 선언되는 클라이언트 컴포넌트를 쓰면 클라이언트 사이드 코드들을 쓸 수 있다- useEffect() 같은 걸 쓰고 싶으면 이쪽으로 옮겨야
- 자동으로 캐시된다
- 보통 캐시 관련 옵션은 Next.js가 알아서 하지만 유저가 행동을 바꿔줄수도 있다
export const dynamic = '';변수를 선언하여 behaviour를 변경할 수 있다force-dynamic: SSR wo/ caching (매 호출시마다 서버에서 렌더링)force-static: SSG, 무조건 페이지를 캐시
export const revalidate = intval을 선언하여 몇초 간 캐시된 내용을 썼다가 시간 경과 후 재렌더링되도록 할 수도 있다 (ISR-equiv)
- SEO 관련:
export const metadata = {}를 선언하여 HTML<meta>태그의 내용을 넣을 수 있다
Data Fetching
- Next.js 13에서 모든 레이아웃과 페이지는 서버 컴포넌트이므로, 서버 사이드 리소스(Environment Variables 등)와 데이터베이스에 접근할 수 있다
- 구버전에서처럼
getServerSideProps(),getStaticProps()써서 컴포넌트에 props를 넘겨주며 삽질할 필요가 없다
- 서버 사이드 컴포넌트는
async await를 이용해 내부에서 직접적으로 data fetching을 할 수 있다.- primsa? async 컴포넌트 안에서
await prisma.getMany();같이 쓰면 된다. - firebase? async 컴포넌트 안에서
await firebase.getDoc();처럼 쓰면 된다. - JS fetch()? async 컴포넌트 안에서
await fetch();해 주면 된다.
- primsa? async 컴포넌트 안에서
- 구버전에서처럼
- 개발이 편해지는 것은 물론, nested component 구조에서는 fetching에 대해 병렬 처리가 이루어지므로 종전 구조 대비 성능도 향상된다
- 사실 Next에서 쓰게 되는
fetch()는 바닐라 JS의 그것이 아니라, React에서 확장해 놓은fetch()이다- 이것은 Automatic Request Deduping에 대응한다 (여러 컴포넌트에서 중복 Fetch 요청 시, 한 번 fetch해서 받은 데이터를 중복 요청한 곳에 다시 갖다줌)
- 또한
cache프로퍼티를 이용해 캐시 동작을 정의해줄 수도 있다- static한 데이터라면
{cache: 'force-cache'}로 캐시 강제 - 항상 바뀌는 데이터라면
{cache: 'no-store'} - 그 중간이라면
revalidate옵션을 넣어 캐시 만료기간을 정해줄 수 있다
- static한 데이터라면
Fetching data from PocketBase
이 강의에서 사용한 PocketBase는 built-in REST API를 가지고 있는, 가제트 만능 단일 바이너리 데이터베이스이다.
async function getNotes() {
const res = await fetch('http://127.0.0.1:8090/api/collections/notes/records?page=1&perPage=30', { cache: 'no-store' });
//를 하면 notes 컬렉션(테이블)에서 30개 단위로 페이지네이션된 레코드를 던져준다.
const data = await res.json();
return data?.items as any[];
//데이터베이스에 있는 데이터의 Array.
}
export default async function NotesPage() {
const notes = await getNotes();
- PocketBase REST API는 이런식으로 return한다:
{ "page": 1, "perPage": 30, "totalItems": 1, "totalPages": 1, "items": [ { "collectionId": "n6nglbveywsg98j", "collectionName": "notes", "content": "hello", "created": "2023-11-10 05:18:34.497Z", "id": "r4ctus4mcew6cdg", "title": "hello world", "updated": "2023-11-10 05:18:34.497Z" } ] }
Server-Rendered이지만 이 Route는 자동으로 캐시된다, Route Segment가 Dynamic이 아니기 때문 (Static Page처럼 취급된다)
때문에 fetch에 , { cache: 'no-store' }를 넣어줘야함
이러면 매 Request마다 아이템을 refetch한다
Dynamic Route: 노트의 제목
http://127.0.0.1:3000/notes/r4ctus4mcew6cdg 와 같은 URL이 노트 상세페이지를 가리키게 하자
/app/notes/[id]/page.tsx생성- 대충 위와 비슷한 소스를 생성한다
async function getNote(noteId: string) {
const res = await fetch(`http://127.0.0.1:8090/api/collections/notes/records/${noteId}`, {next:{revalidate: 10}});
const data = await res.json();
return data;
}
export default async function NotePage({params}: any) {
const note = await getNote(params.id);
return(
<div>
<h1>notes</h1>
<div>
<h3>{note.title}</h3>
<h5>{note.content}</h5>
<p>{note.created}</p>
</div>
</div>
);
}
- fetch()에서
cache:'no-store'를 넣을 필요는 없는데, Dynamic Route이기 때문에 매 Request마다 fetch하는 것이 default이기 때문- 하지만,
next: {revalidate: 10}과 같은 옵션을 넣어, ISR (Incremental Static Regeneration) 을 구현할 수 있음- 캐싱된 페이지가 10초보다 낡았으면 페이지를 재생성
- 하지만,
로딩 화면
loading.tsx 파일 생성
"Interactive"한 CreateNote component
'use client';: 서버에서 렌더링하지 않고 브라우저에서 렌더링- React의
useStateHook을 이용해 title, content에 대한 field를 추가
- 잠깐 - 리액트 톺아보기:
- Hook: 리액트 프레임워크의 다양한 기능을 사용하기 위하여 컴포넌트의 TOP LEVEL에서 사용될 수 있는 함수
- useState() 훅을 사용하면 Stateful한 Value를 만들어, 변경될 때마다 여기에 의존하는 컴포넌트들이 자동으로 재렌더링되게할수있다.
- e.g.,
const [title, setTitle] = useState('');- 기본값을
''으로, 반환하는 것은[title, setTitle]로 구성된 Array - 배열의 첫 번째는 UI에서 사용할 실제 값으로, 두 번째는 함수로 구성한다
- 기본값을
- onChange 때마다 setTitle(), setContent()를 각기 불러 state를 update
'use client';
import { useState } from "react";
export default function CreateNote() {
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const create = async() => {
await fetch('http://127.0.0.1:8090/api/collections/notes/records',
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({title, content})
}
);
}
return(
<form onSubmit={create}>
<h3>create a new note</h3>
<input
type="text" placeholder="title" value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<textarea
placeholder="content" value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button type="submit">create note</button>
</form>
);
}
- create 내장-함수 만들기: 클라의 폼 내용을 PocketBase REST API에 fetch()
새로고침을 하지 않아도
import { useRouter } from "next/navigation";
...
router.refresh();
Streaming
- 일반적으로 Next 웹페이지 렌더링은 다음의 서순을 따른다
- 서버에서 데이터를 fetch한다
- React 컴포넌트를 서버에서 HTML로 렌더링한다
- 서버는 HTML을 브라우저에 보낸다
- 브라우저는 HTML/CSS를 렌더링한다 (Non-Interactive Page)
- 브라우저에서 JS가 실행되어 Hydrated되며 Interactive한 페이지가 완성된다
- 이 과정은 순차적으로, 데이터를 많이 fetch해야 하는 큰 웹사이트에서는 많은 데이터를 로드해야할 수 있고 그러면 사이트가 느려진다
- Next.js 13에서는 페이지를 컴포넌트별로 조각조각 쪼개서 페이지를 프로그레시브하게 로드한다
- 이것을 페이지 스트리밍이라고 하는데 사실 Next가 알아서 하는 부분이기는 한다
- 하지만
loading.tsx와 같은 파일을 라우트에 추가하여 UX를 향상시킬 수 있다- 이러면 다른 컴포넌트가 로딩되는 사이에 loading.tsx의 내용이 표시된다
- 이 알잘딱의 비결은 Suspense이다
- React에서, Suspense는 suspense boundary를 생성하는 특수 컴포넌트이다
- 데이터 fetching같이 async한 동작을 하는 컴포넌트를 감싸주고, async operation이 끝날 때까지 fallback UI를 표시한다
Auth.js를 통한 로그인
- 기본적으로 jwt (JSON Web token) - 암호화된 토큰을 클라이언트 사이드에 저장
- 데이터베이스에 저장할 수도 있음
- 알아서 로그인 시나리오를 커버해주는 다양한 API Route를 생성
- 이후 Application의 root에
<SessionProvider>를 추가
export default function AuthProvider({children}: Props) {
return <SessionProvider>{children}</SessionProvider>;
}
- 모든 자식들은
useSession()Hook을 이용해 사용자에 대한 갱신사항을 리얼타임으로 들을수있음
'use client';
import { useSession } from 'next-auth/react';
export default function AuthCheck({children} : {children: React.ReactNode}) {
const {data: session, status} = useSession();
}
- 또한 로그인, 로그아웃을 위한 함수도 제공됨.
signIn()의 경우 버튼에 바운드시켜주면 전용 페이지로 이동
export function SignOutButton() {
return <button onClick{() => signOut()}>Sign Out!</button>;
}
- 서버 사이드에서는,
getServerSession()훅을 이용해서 로그인 상태를 받을 수 있음
반응형
'콤퓨우터 > 프로그래밍' 카테고리의 다른 글
| Next.js Full Course 필기 (4) (0) | 2024.03.17 |
|---|---|
| Next.js Full Course 필기 (3) (0) | 2024.03.17 |
| Next.js Full Course 필기 (2) (0) | 2024.03.17 |
| 아희와 Hello, Wolrd! - 1문자씩 분석해보았다 (2) | 2017.06.30 |