개발자는 서버 및 클라이언트 구성요소를 사용하여 클라이언트 측 응용프로그램의 풍부한 상호 작용과 기존 서버 렌더링의 향상된 성능을 결합하여 서버 및 클라이언트에 걸친 응용프로그램을 구축할 수 있습니다.
React는 전체 응용프로그램 클라이언트 측(예: 단일 페이지 응용프로그램)을 렌더링하는 대신, 이제 React를 사용하면 목적에 따라 구성요소를 렌더링할 위치를 유연하게 선택할 수 있습니다.
서버 구성요소를 사용하여 개발자는 서버 인프라를 보다 효과적으로 활용할 수 있습니다. 예를 들어, 이전에 클라이언트의 JavaScript 번들 크기에 영향을 주었던 대규모 종속성이 서버에 남음으로써 성능을 향상시킬 수 있습니다. 그들은 React 애플리케이션을 작성하는 것을 PHP나 Ruby on Rails와 유사하게 느끼지만, React의 힘과 유연성과 UI를 템플릿화하기 위한 구성 요소 모델을 사용합니다.
서버 구성요소를 사용하면 초기 페이지 로드 속도가 빨라지고 클라이언트 측 JavaScript 번들 크기가 줄어듭니다. 기본 클라이언트 측 런타임은 캐시가 가능하고 크기를 예측할 수 있으며 응용프로그램이 증가함에 따라 증가하지 않습니다. 추가 JavaScript는 클라이언트 구성요소를 통해 응용프로그램에서 클라이언트 측 상호작용이 사용되는 경우에만 추가됩니다.
경로에 Next.js가 로드되면 초기 HTML이 서버에서 렌더링됩니다. 그런 다음 이 HTML은 브라우저에서 점진적으로 향상되어 클라이언트가 응용프로그램을 인계받고 클라이언트 측 런타임을 비동기식으로 로드하여 상호 작용을 추가할 수 있습니다.
"use client"는 서버 전용 코드와 클라이언트 코드 사이에 위치합니다. 서버에서 클라이언트 부분까지의 경계를 통과하는 차단점을 정의하기 위해 가져오기 위의 파일 맨 위에 배치됩니다. 파일에 "use client"가 정의되면 하위 구성요소를 포함하여 파일로 가져온 다른 모든 모듈이 클라이언트 번들의 일부로 간주됩니다.
Good to know:
서버 구성요소 모듈 그래프의 구성요소는 서버에서만 렌더링됩니다.
클라이언트 구성요소 모듈 그래프의 구성요소는 주로 클라이언트에서 렌더링되지만 Next.js를 사용하면 서버에서 미리 렌더링하고 클라이언트에서 하이드레이팅할 수도 있습니다.
가져오기 전에 파일의 맨 위에 "use client" 지시사항을 정의해야 합니다.
모든 파일에 "사용자 클라이언트"를 정의할 필요는 없습니다. 클라이언트 모듈 경계는 "입구점"에서 한 번만 정의하면 클라이언트 모듈 경계로 가져온 모든 모듈이 클라이언트 구성요소로 간주됩니다.
앱의 성능을 향상시키기 위해선 클라이언트 컴포넌트를 우리의 컴포넌트 트리의 리프로 이동시키는것을 추천합니다. layout을 클라이언트 컴포넌트로 만드는 대신, interactive logic을 클라이언트 컴포넌트로 옮기고 layout을 서버 컴포넌트로 유지시킵니다. 이는 레이아웃의 모든 컴포넌트의 자바스크립트를 고객(사용자)에게 보낼 필요가 없다는것을 의미합니다.
// SearchBar is a Client Component
import SearchBar from './searchbar';
// Logo is a Server Component
import Logo from './logo';
// Layout is a Server Component by default
export default function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
<>
<nav>
<Logo />
<SearchBar />
</nav>
<main>{children}</main>
</>
);
}
서버와 클라이언트 컴포넌트는 같은 컴포넌트 트리에서 결합될 수 있습니다. 화면 뒤에서 리액트는 다음과 같이 rendering을 다룹니다.
Good to know:
Next.js에서는 초기 페이지 로드 중에 위 단계의 서버 구성요소 및 클라이언트 구성요소의 렌더링 결과가 모두 서버에서 HTML로 미리 렌더링되어 더 빠른 초기 페이지 로드를 생성합니다.
위에서 설명한 렌더링 흐름을 고려할 때, 서버 구성요소를 클라이언트 구성요소로 가져오는 데는 추가 서버 왕복이 필요하므로 제한이 있습니다.
다음과 같은 패턴은 지원하지 않습니다. 우리는 서버 컴포넌트를 클라이언트 컴포넌트 안에서 import할 수 없습니다.
'use client';
// This pattern will **not** work!
// You cannot import a Server Component into a Client Component.
import ExampleServerComponent from './example-server-component';
export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode;
}) {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ExampleServerComponent />
</>
);
}
대신, 클라이언트 컴포넌트를 디자인할 때, 우리는 React의 props를 사용해서 서버 컴포넌트를 위한 holes를 만들 수 있습니다.
클라이언트 컴포넌트가 client위에서 그려지면, hole은 서버 컴포넌트의 결과물로 채워질 것입니다.
클라이언트 컴포넌트에서는 children으로 서버 컴포넌트를 받습니다.
'use client';
import { useState } from 'react';
export default function ExampleClientComponent({
children,
}: {
children: React.ReactNode;
}) {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
);
}
// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ExampleClientComponent from './example-client-component';
import ExampleServerComponent from './example-server-component';
// Pages in Next.js are Server Components by default
export default function Page() {
return (
<ExampleClientComponent>
<ExampleServerComponent />
</ExampleClientComponent>
);
}
이 방법을 사용하면 ExampleClientComponent
와 ExampleServerComponent
의 렌더링이 분리되고 클라이언트 구성요소보다 먼저 서버에 렌더링되는 서버 구성요소에 맞춰 독립적으로 렌더링될 수 있습니다.
Good to know
- 이 합성 전략은 서버 컴포넌트와 클라이언트 컴포넌트 간에 작동합니다. 이 컴포넌트는 제안을 수신하는 구성요소가 무엇인지 알지 못하기 때문입니다. 그것은 단지 그것이 전달된 것이 어디에 놓여야 하는지에 대한 책임이 있습니다.
- 이렇게 하면 클라이언트 구성요소가 클라이언트에서 렌더링되기 훨씬 전에 전달된 프롭을 서버에서 독립적으로 렌더링할 수 있습니다.
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
});
return res.json();
}
얼핏 보기에는 서버와 클라이언트 양쪽 모두에서 getData 함수가 동작할 것처럼 보이지만, API_KEY 앞에 NEXT_PUBLIC prefix가 빠져있으므로, 오직 서버에서만 접근 가능하다. Next.js는 client code에서 개인 환경 변수를 보안정보 유출 방지를 위해 빈 문자열로 치환합니다.
결과적으로, client에서 getData()함수를 import하고 실행할수는 있지만, 기대한대로 동작하지는 않을것입니다. 그렇다고 변수를 공개하면 함수는 제대로 동작하겠지만, 민감한 정보가 유출될 것입니다.
그래서, 이 함수는 오직 서버에서만 동작하도록 작성되었습니다.
이러한 종류의 의도하지 않은 클라이언트의 서버 코드 사용을 방지하기 위해 다른 개발자가 실수로 이러한 모듈 중 하나를 클라이언트 구성요소로 가져오는 경우 서버 전용 패키지를 사용하여 빌드 시간 오류를 발생시킬 수 있습니다.
npm install server-only
import 'server-only';
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
});
return res.json();
}
이제 getData()를 가져오는 모든 클라이언트 구성 요소는 이 모듈을 서버에서만 사용할 수 있음을 설명하는 빌드 시간 오류를 수신합니다.
해당 패키지 클라이언트 전용은 window 객체에 액세스하는 코드와 같은 클라이언트 전용 코드가 포함된 모듈을 표시하는 데 사용할 수 있습니다.
클라이언트 구성요소에서 데이터를 가져올 수 있지만 클라이언트에서 데이터를 가져오는 특별한 이유가 없는 한 서버 구성요소에서 데이터를 가져오는 것이 좋습니다. 데이터 가져오기를 서버로 이동하면 성능과 사용자 환경이 개선됩니다.
서드 파티 라이브러리를 사용하는 경우, 아직 많은 경우에 use client-only directive를 사용하지 않습니다. 그렇기에 클라이언트 컴포넌트에서는 기대한대로 동작하겠지만, 서버 컴포넌트에서는 그렇지 않습니다. 그런 경우 다음과 같이 사용할 수 있습니다.
'use client';
import { AcmeCarousel } from 'acme-carousel';
export default AcmeCarousel;
import Carousel from './carousel';
export default function Page() {
return (
<div>
<p>View pictures</p>
{/* Works, since Carousel is a Client Component */}
<Carousel />
</div>
);
}
리액트 관련해서 여기 좋은 글이 있다.
React18: Suspense를 이용한 새로운 SSR 아키텍쳐를 굉장히 재미있게 읽었다.
해당글의 결론을 보면
React 18은 SSR에 있어 두 개의 주요한 기능들을 제공한다:
script
태그와 함께 스트리밍 형태로 보낼 수 있게 해준다.이 기능들은 React의 SSR이 오랜 기간 동안 가지고 있는 세 개의 문제를 해결한다.
13.4버전이 나온 기념으로 공식문서 탐방이나 해야겠다~