[Next.js] Next.js 복습 (1) - Next.js 소개 ~ style
이번 파이널 프로젝트 들어가기 전까지 Next.js에 대해 공부했다. 비록 파이널 프로젝트는 React를 사용하지만 그래도 Next.js는 알아야 한다고 생각했기에 제로초님 강의를 들었고 지금까지 배운 내용을 복습하고자 한다.
1. Next.js란?
Next.js는 React의 프레임워크이다
반대로 React는 라이브러리인데 라이브러리는 어플리케이션을 만들 때 필요한 자원의 모임이고 프레임워크는 기본적인 틀을 제공해서 보다 효율적으로 어플리케이션을 만들 수 있도록 하는 소프트웨어 환경이다.
Next.js는 서버사이드 렌더링(SSR) , 정적 사이트 생성(SSG)과 같은 기능들을 제공한다.
2. SSR과 SSG란?
1) SSR
웹 페이지를 브라우저에서 렌더링 하는 대신, 서버에서 렌더링 한다. 브라우저가 서버의 URI로 GET 요청을 보내면 서버는 정해진 웹 페이지 파일을 브라우저로 전송 하고 브라우저에 도착하면 완전히 렌더링 된다. 즉, 서버에서 렌더링 작업이 끝나고 브라우저로 보내기 때문에 SEO 같은 면에서 매우 큰 장점이 있다.
2) CSR
Clident Side Rendering의 줄임말로 클라이언트에서 페이지를 렌더링 한다. 클라이언트 즉 , 웹 브라우저에서 요청을 서버로 보내면 서버는 웹 페이지의 골격이 될 단일 페이지를 클라이언트에 보내면서 JS파일을 함께 보낸다. 클라이언트가 웹 페이지를 받으면, JS 파일은 브라우저에서 웹 페이지를 완전히 렌더링 된 페이지로 바꾼다. 또한 DB의 내용이 필요할 땐 API를 사용한다. 사이트에서 사용자와 상호 작용이 많을 경우에 빠른 라우팅으로 유용하게 사용된다.
3) SSG
Static Site Generation의 줄임말로 Next.js에서 페이지를 생성할 때 기본으로 적용되는 설정이다. SSR과 다른 점은 클라이언트가 요청하는 시점이 아닌 빌드 시 페이지를 미리 생성해두는 것이다. Data Fetching이 필요할 땐 getStaticProps 라는 함수를 export해서 그 함수 내에서 데이터를 받아서 리턴하면 빌드 시 getStaticProps가 실행되고 리턴하는 값을 컴포넌트에서 받아 미리 렌더링하게 된다.
4) ISR
Incremental Static Regeneration의 줄임말로 빌드 시점에 페이지를 렌더링 한 후 설정한 시간 마다 페이지를 새로 렌더링 한다.
3. 핵심 기능
1) 라우팅
13 버전까진 페이지, 앱 라우팅 모두 쓰였는데 14부터는 앱 라우팅이 주가 되는 것 같다. Next.js 공식 문서에서도 앱 라우팅을 말하고 있다.
app/page.js 이런 식으로 app에서 출발하는 것을 앱 라우팅이라고 한다. React보다 훨씬 더 편하다는 장점이 있다.
아래 예시로 보면 app/home/page.tsx로 간단하게 구현 가능한 것을 알 수 있다. 이를 로컬에서 실행하면
http://localhost:3000/home 으로 찍힌다.
또한 app/page.tsx로 바로 보여주는 방법과 app/layout.tsx 방법 두 가지가 있다. layout는 A layout is UI that is shared between multiple pages. 라고 공식문서에서 나와 있는데 즉, 다양한 페이지 간의 화면을 보여주는 것이다. {children} prop으로 보여줄 수 있다. 꼭 app에만 해당되는 것은 아니고 다양하게 표현 가능하다. app에서 출발하는 것은 RootLayout 이라고 한다.
app/layout.tsx
export default function RootLayout({ children }: Props) {
return (
<html lang="en">
<body className={inter.className}>
<MSWComponent />
<AuthSession>{children}</AuthSession>
</body>
</html>
);
}
※ React.ReactNode / React.ReactElement / React.ReactChild
1) React.ReactNode 타입
: jsx 내에서 사용할 수 있는 모든 요소의 타입을 의미 함
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
2) React.ReactElement
: createElement 함수를 통해 생성된 객체의 타입, 원시타입을 허용하지 않고 완성된 jsx 요소만을 허용함. ReactNode가 ReactElement를 포함하는 관계
3) React.ReactChild
: ReactElement에서 원시타입까지 허용 하는 타입
type ReactChild = ReactElement | ReactText;
또한 Next.js는 Dynamic Routing을 지원하는데 폴더를 [username]으로 감싸주면 된다.
// app/blog/[slug]/page.tsx
export default function Page({ params }: { params: { slug: string } }) {
return <div>My Post: {params.slug}</div>
}
그리고 Catch-all 라우트도 있는데 app/shop/[...slug]/page.js를 이렇게 써주면 그 안에 뭐가 들어가냐에 따라 /shop/a , /shop/a/b/c ..등 다양하게 들어갈 수 있다.
2) 라우팅 그룹
주소창에는 반영이 안되지만 이들 간의 그룹을 짓고 싶을 때 사용한다. 위 사진처럼 (afterLogin)으로 그룹을 만들어 줄 수 있다. (afterLogin)과 (beforeLogin) 이렇게 만들어주면 이 들 각각 다른 layout을 만들어 줄 수 있다는 장점이 있다.
ex) (afterLogin)/layout.tsx & (beforeLogin)/layout.tsx
3) Linking and Navigating
Next.js는 Link와 Navigation을 지원한다.
Link는 next/link로 prefetching 한 것들과 client-side 간의 navigation을 지원한다. Link 컴포넌트에는 스타일이나 클래스 등을 부여할 수 없다는 단점이 있어 안에 <a> 태그를 내부에 넣어준다고도 한다.
import Link from 'next/link'
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
또한 usePathname() 이란 것으로 active Link를 활성화 해줄 수 있다. 아래 코드처럼 active를 걸어줄 수 있으며 css 효과를 넣어주면 해당 탭 위치임을 표시 할 수 있다. 혹은 useRouter()로도 현재 url에 대한 데이터를 가져와서 사용 가능하다. (router.pathname)
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}
4) style
module.css를 지원한다. home.module.css 이런 식으로 작성하며 또한 styled JSX라는 독특한 Next.js 만의 스타일 선언 방식이 있다. 랜덤 해시값을 적용하여 다른 컴포넌트와 스타일이 충돌되는 것을 막는다 사용법은 간단하다.
import style from "./profile.module.css";
...
return (
<main className={style.main}>
<HydrationBoundary state={dehydratedState}>
<UserInfo username={username} />
<div>
<UserPosts username={username} />
</div>
</HydrationBoundary>
</main>
);
}