반응형
옛날에 위의 영상을 보고 공부했던 내용을 개인 필기용으로 적어둔 것입니다
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의
useState
Hook을 이용해 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 |