overreacted 블로그 포스트 번역
React Server Components (RSC)는 클라이언트/서버 애플리케이션을 두 환경에 걸쳐 있는 단일 프로그램으로 표현할 수 있게 해주는 프로그래밍 패러다임입니다. 구체적으로, RSC는 모듈 시스템(import와 export 키워드)을 확장하여 개발자가 프론트엔드/백엔드 분할을 제어할 수 있는 새로운 의미론을 제공합니다.
저는 이전에 작성한 글에서 두 환경 간의 "분할점"을 표시하는 'use client'와 'use server' 지시문에 대해 다루었습니다. 이 글에서는 이러한 지시문이 import와 export 키워드와 어떻게 상호작용하는지에 초점을 맞추고 싶습니다. 이 글은 RSC의 정확한 멘탈 모델을 구축하고 싶은 사람들, 그리고 일반적으로 모듈 시스템에 관심 있는 사람들을 위한 깊이 있는 탐구입니다. RSC 접근 방식이 당신이 생각하는 것보다 놀랍기도 하고 더 간단할 수도 있다는 것을 알게 될 것입니다.
평소처럼, 이 글의 90%는 RSC 에 대한 것이 아닙니다. 일반적으로 import가 어떻게 작동하는지, 그리고 백엔드와 프론트엔드 간에 코드를 공유하려고 할 때 무엇이 일어나는지에 관한 것입니다. 제 목표는 RSC가 양쪽 경계에 걸쳐 코드를 작성할 때 발생하는 마지막 10%의 문제점들에 대해 어떻게 자연스러운 해결책을 제공하는지 보여주는 것입니다.
기초부터 시작해봅시다.
모듈 시스템이란 무엇인가?
컴퓨터가 프로그램을 실행할 때, "모듈"이 필요하지 않습니다. 컴퓨터는 프로그램의 코드와 데이터가 실행하고 처리하기 전에 메모리에 완전히 로드되어야 합니다. 실제로는 우리 인간이 코드를 모듈로 분할하고 싶어 합니다:
- 모듈은 복잡한 프로그램을 우리 뇌에 맞을 수 있는 부분으로 나눌 수 있게 해줍니다.
- 모듈은 코드의 어느 부분이 다른 부분에 보이도록 의도된 것인지(또는 내보내진 것인지), 그리고 어느 부분이 구현 세부사항으로 남아야 하는지를 제한할 수 있게 해줍니다.
- 모듈은 다른 사람들(그리고 우리 자신)이 작성한 코드를 재사용할 수 있게 해줍니다.
우리는 프로그램을 부분으로 분할된 상태로 작성하고 싶지만, 프로그램을 실행하는 것은 메모리에서 그 부분들을 "펼치는" 것을 포함합니다. 모듈 시스템의 역할은 인간이 코드를 작성하는 방식과 컴퓨터가 코드를 실행하는 방식 사이의 간격을 메우는 것입니다.
구체적으로, 모듈 시스템은 프로그램을 파일로 분할하는 방법, 개발자가 어느 부분이 다른 부분을 "볼" 수 있는지를 제어하는 방법, 그리고 메모리에 로드될 수 있는 단일 프로그램으로 그 부분들을 연결하는 방법을 지정하는 규칙 집합입니다.
JavaScript에서 모듈 시스템은 import와 export 키워드를 통해 노출됩니다.
Import는 복사-붙여넣기 같습니다…
a.js와 b.js라고 부를 두 파일을 생각해봅시다:
그 자체로는 함수를 정의하는 것 외에는 아무것도 하지 않습니다.
이제 index.js라고 불리는 이 파일을 생각해봅시다:
이제 그들을 단일 프로그램으로 묶는 모듈입니다!
JavaScript 모듈 시스템의 규칙은 복잡합니다. 작동 방식에 많은 미묘한 점이 있습니다. 하지만 우리가 사용할 수 있는 간단한 직관이 있습니다. JavaScript 모듈 시스템은 위의 프로그램이 실행될 때, 모듈을 사용하지 않는 이 단일 파일 프로그램과 동일하게 작동해야 한다는 것을 보장하도록 설계되었습니다:
다시 말해, import와 export 키워드는 복사-붙여넣기를 연상시키는 방식으로 작동하도록 설계되었습니다—궁극적으로, 결국 프로그램은 JS 엔진에 의해 프로세스의 메모리에서 "펼쳐져야" 하기 때문입니다.
…하지만 그렇지 않습니다
앞서 import는 복사-붙여넣기 같다고 말했습니다. 그것은 정확히 사실이 아닙니다. 왜인지 보기 위해, C의 #include 지시문으로 시간 여행을 떠나는 것이 도움이 됩니다.
#include 지시문은 JavaScript import보다 약 40년 앞서 있으며, 문자 그대로 복사-붙여넣기처럼 작동했습니다! 예를 들어, 여기 C 프로그램이 있습니다:C에서 #include 지시문은 위의 파일에 a.h와 b.h의 전체 내용을 문자 그대로 포함시킵니다. 이 동작은 간단하지만 두 가지 큰 단점이 있습니다:
#include의 또 다른 문제는 같은 파일이 여러 곳에서 "포함"될 수 있다는 것입니다—따라서 출력 프로그램에서 여러 번 반복됩니다! 이를 해결하기 위해 모범 사례는 "포함 가능"하기를 원하는 각 파일의 내용을 빌드 타임 "이미 포함했으면 나를 건너뛰세요" 가드로 감싸는 것이었습니다. import와 같은 최신 모듈 시스템은 이를 자동으로 수행합니다.
마지막 포인트를 풀어봅시다. 왜냐하면 그것이 중요하기 때문입니다.
JavaScript 모듈은 싱글톤입니다
c.js라고 불리는 새로운 모듈을 추가했다고 가정합시다:
이제 a.js와 b.js 모두를 다시 작성했다고 가정합시다. 그래서 각각이 c.js 파일에서 c 함수를 import하고 그것으로 뭔가를 합니다:
만약 import가 문자 그대로 복사-붙여넣기였다면(#include처럼), 우리는 프로그램에서 c 함수의 두 복사본을 끝내게 될 것입니다. 하지만 다행히, 그것은 일어나지 않습니다!
JavaScript 모듈 시스템은 위의 코드가 이전의 index.js 파일과 함께 아래의 단일 파일 프로그램과 의미론적으로 동등하다는 것을 보장합니다. c 함수가 두 번 import되었음에도 불구하고 한 번만 정의되는 방식에 주목하세요:
다시 말해, 최신 모듈 시스템(예: JavaScript 모듈 시스템)은 각 개별 모듈 내의 코드가 최대 한 번 실행된다는 것을 보장합니다. 그 모듈이 얼마나 많은 곳에서 얼마나 많이 import되든 상관없습니다.
이것은 많은 이점을 가능하게 하는 중요한 설계 선택입니다:
- 코드가 단일 프로그램으로 변환될 때(실행 파일, 번들, 또는 메모리 내), 출력 크기는 반복으로 인해 "폭발"하지 않습니다.
- 각 모듈은 최상위 변수에서 일부 "개인 상태"를 유지할 수 있으며, 얼마나 많이 import되었든 상관없이 유지된다는 것을 확신할 수 있습니다.
- 각 모듈이 "싱글톤"이기 때문에 정신 모델이 극적으로 더 간단합니다. 어떤 코드가 한 번만 실행되기를 원한다면, 그것을 모듈의 최상위 수준에 작성하세요.
다시 한 번 말하겠습니다: 각 JavaScript 모듈은 싱글톤입니다. 같은 모듈을 두 번 import하면 그 코드가 두 번 실행되지 않습니다. 모든 모듈은 최대 한 번 실행됩니다.
우리는 여러 모듈에 대해 이야기했지만, 여러 컴퓨터는 어떨까요?
한 프로그램, 한 컴퓨터
대부분의 JavaScript 프로그램은 단일 컴퓨터용으로 작성됩니다.
그것은 브라우저, Node.js 서버, 또는 일부 이국적인 JavaScript 런타임일 수 있습니다. 여전히, 대부분의 JS 프로그램이 단일 머신에서 실행되도록 작성된다고 말하는 것이 안전하다고 생각합니다. 프로그램이 로드되고, 프로그램이 실행되고, 프로그램이 중지됩니다.
JavaScript 모듈 시스템은 앞서 설명한 대로 정확히 이 가장 일반적인 사용 사례를 지원하도록 설계되었습니다. 작동 방식의 마지막 요약은 다음과 같습니다:
- 프로그램의 진입점 역할을 하는 어떤 파일이 있습니다. 우리의 이전 예제에서, 그것은
index.js였습니다. 이것이 JavaScript 엔진이 시작하는 곳입니다.
- 이 파일은
a.js 또는 b.js와 같은 다른 모듈을 import할 수 있으며, 이들 자체가 더 많은 모듈을 import할 수 있습니다. JavaScript 엔진은 그 모듈들의 코드를 실행합니다. 또한 각 모듈의 내보낸 값을 나중을 위해 메모리 내 캐시에 저장합니다.
- JavaScript 엔진이 이미 로드한 모듈에 대한
import를 보면(c.js에 대한 두 번째 import와 같이), 그것은 모듈을 다시 실행하지 않을 것입니다. 모듈은 싱글톤입니다! 대신, 메모리 내 캐시에서 그 모듈의 내보낸 값을 읽을 것입니다.
궁극적으로, 최종 결과를 모듈을 한 파일에 복사-붙여넣기하고, 충돌하는 변수들을 외과적으로 이름 바꾸고, 각 개별 모듈의 내용이 한 번만 포함되도록 보장하는 것과 유사하다고 생각하는 것이 편합니다:
그런 의미에서, import할 때 어떤 코드를 프로그램에 가져옵니다.
두 프로그램, 두 컴퓨터
전통적으로, JS의 프론트엔드와 백엔드는 두 개의 서로 다른 컴퓨터에서 실행되는 두 개의 다른 프로그램을 의미합니다. 많은 경우, 그들은 거의 서로 말하지 않는 두 개의 다른 팀에 의해 유지될 수도 있습니다.
이 두 프로그램을 자세히 살펴봅시다. 백엔드는 HTML 페이지(그리고 잠재적으로 더 데이터 집약적인 앱을 위한 일부 API)를 제공하는 책임이 있습니다. 프론트엔드는 그 HTML 페이지의 대화형 로직 조각들을 담당합니다.
백엔드 코드는 backend/index.js에 있을 수 있습니다:
프론트엔드 코드는 frontend/index.js에 있을 수 있습니다:
이 두 프로그램이 별개이지만 관련이 있다는 것을 강조하기 위해 그들을 가깝게 놓겠습니다:
이제 어느 쪽에서든 뭔가를 import할 때 무엇이 일어나는지 봅시다.
backend/index.js에서 a.js와 b.js를 import한다고 가정합시다:
그들을 백엔드 코드에서 import하면 그들을 백엔드 코드에 가져옵니다:
이제 frontend/index.js에서도 그들을 import한다고 가정합시다:
프론트엔드 코드에서 그들을 import하면 그들을 프론트엔드 코드에 가져옵니다:
프론트엔드와 백엔드가 모듈 시스템을 공유하지 않는다는 점에 주목하세요!
이것은 중요한 통찰입니다. 어느 쪽에서든 코드를 import하면 그 코드를 그 쪽에 가져옵니다—그 이상도 이하도 아닙니다. 두 쪽은 두 개의 독립적인 모듈 시스템을 가집니다. 모듈은 여전히 싱글톤처럼 작동합니다—하지만 그들은 환경별로만 싱글톤입니다.
우리가 a.js, b.js, 그리고 c.js 구현을 양쪽 간에 재사용하고 있지만, 백엔드 코드와 프론트엔드 쪽이 a.js, b.js, 그리고 c.js 모듈의 "자신의 버전"을 가지고 있다고 생각하는 것이 더 정확할 것입니다.
지금까지, 제가 설명한 것에는 특별한 것이 없습니다. 이것이 전체 스택 앱에서 프론트엔드와 백엔드 간에 코드를 공유하는 방식입니다. 그러나 환경 간에 더 많은 코드가 재사용될수록, 우리는 다른 쪽에 의도되지 않은 뭔가를 실수로 재사용할 위험이 있습니다.
코드 재사용을 어떻게 제한하고 제어할 수 있을까요?
빌드 실패는 실제로 좋습니다
누군가가 c.js를 편집하여 오직 백엔드에서만 의미가 있는 코드를 포함한다고 가정합시다. 예를 들어, 서버에서 파일을 읽기 위해 fs를 사용한다고 상상해봅시다:
이것은 백엔드 코드에 문제를 일으키지 않을 것입니다:
그러나 fs가 그곳에 존재하지 않기 때문에 프론트엔드 빌드에 실패할 것입니다:
그리고 이것은 실제로 좋습니다!
두 쪽 간에 코드를 재사용하기 시작할 때, 우리는 재사용하려는 코드가 실제로 양쪽에서 작동할 것이라는 확신을 가지고 싶습니다.
어떤 API가 한쪽에서만 의미가 있다면(fs는 백엔드에서만 의미가 있음), 우리는 빌드가 조기에 실패하기를 원합니다. 그래서 우리는 코드를 어떻게 수정할지 결정할 수 있습니다:
fs 호출을 c.js 이외의 다른 곳으로 이동하도록 선택할 수 있습니다.
a.js와 b.js를 리팩토링하여 c.js가 필요하지 않도록 할 수 있습니다.
frontend/index.js를 변경하여 a.js와 b.js가 필요하지 않도록 할 수 있습니다.
위의 모든 솔루션이 유효하다는 것을 주목하는 것이 중요합니다. 선택하는 솔루션은 실제로 무엇을 하려고 하는지에 따라 다릅니다. 어떤 솔루션이 "최고"인지 선택하는 자동화된 방법은 없습니다—만약 무엇이든, 이것은 실제 Git 충돌을 해결하는 것과 유사합니다. 해결하는 것이 재미있지는 않지만 원하는 동작은 당신(또는 LLM)이 결정해야 합니다.
이것이 코드를 재사용하기 위해 지불하는 대가입니다. 이점은 어느 쪽이 필요한지에 따라 로직을 쉽게 이동할 수 있다는 것입니다. 단점은 상황이 폭발할 때 빌드 실패를 보고 어느 모듈이 수정이 필요한지 결정해야 한다는 것입니다.
이 경우, 우리는 "잘못된 쪽"에서 뭔가를 import하는 것이 실제로 빌드 오류를 일으켰다는 운이 좋았습니다. 이것은 우리가 즉시 문제를 볼 수 있게 했습니다. 하지만 그렇지 않았다면 어떨까요?
서버 전용 코드
대신, 누군가가 c.js를 편집하여 서버 측 비밀을 import한다고 가정합시다.
이것은 이전 예제보다 훨씬 더 나쁩니다! 빌드 실패가 없을 것이고, secret은 백엔드 그리고 프론트엔드 코드의 일부가 될 것입니다:
이것은 악몽 같은 시나리오이지만, 많은 풀스택 앱이 개발자가 실수로 프론트엔드 코드에 비밀을 끌어들이는 것에 대한 보호를 사용하지 않습니다!
우리가 이것을 개선할 수 있을까요?
여기 한 가지 아이디어가 있습니다. 이전 섹션에서, 우리는 프론트엔드 코드에서 fs를 사용하면 프론트엔드 빌드에 실패했다는 것을 보았습니다. 이것은 우리가 여기서도 일어나기를 정확히 원하는 것입니다!
server-only라고 부를 특별한 패키지를 만든다고 가정합시다. 이것은 프론트엔드에 절대 도달해서는 안 되는 코드의 마커 역할을 합니다. 그 자체로, 그 패키지는 실제 코드를 포함하지 않을 것입니다. 이것은 "독 알약"입니다. 우리는 프론트엔드 번들러에게 이 모듈이 프론트엔드 번들에 들어가면 빌드에 실패하도록 가르칠 것입니다.
우리가 그렇게 했다고 가정하면, 이제 secrets.js를 서버 전용으로 표시할 수 있습니다:
이 변경으로, secrets.js를 번들에 끌어들이면 프론트엔드 빌드에 실패합니다. 구체적으로, a.js와 b.js 모두 c.js를 가져올 것이고, 이것은 secrets.js를 가져올 것이고, 이것은 server-only를 가져올 것입니다—그리고 그것이 빌드에 실패하는 독 알약입니다:
이제 어떤 코드가 백엔드를 벗어나지 않도록 제어할 수 있습니다! (구체적인 구현 예제로, 여기 Next.js 번들러의 관련 로직이 있습니다.) 이전의 fs import처럼, 우리는 그것을 수정하기 위한 다양한 옵션을 가질 것입니다:
secrets.js import를 c.js 이외의 다른 곳으로 이동하도록 선택할 수 있습니다.
a.js와 b.js를 리팩토링하여 c.js가 필요하지 않도록 할 수 있습니다.
frontend/index.js를 변경하여 a.js와 b.js가 필요하지 않도록 할 수 있습니다.
하지만 이 솔루션의 중요한 부분은 그것이 자동으로 import 체인을 따라 전파된다는 것입니다. a.js, b.js, 그리고 c.js와 같은 개별 파일을 서버 전용으로 표시할 필요가 없습니다. 그들에 로컬한 어떤 특정한 이유가 있지 않는 한 말입니다. 절대 포함되어서는 안 되는 파일(예: secrets.js)을 표시하고, "독 알약"이 import 체인을 따라 전파되도록 하는 것으로 충분합니다.
클라이언트 전용 코드
server-only "독 알약"과 유사하게, 우리는 미러 트윈 client-only "독 알약"을 만들 수 있습니다. 이것은 서버 측 빌드에 실패합니다. (서버를 번들하지 않으면, TypeScript를 실행하는 것과 유사하게 대신 이 검사를 별도로 실행할 수 있습니다.)
c.js에서 브라우저 특정 API를 사용했다고 가정합시다. 이것은 백엔드 코드에 절대 끌어들이는 것이 유효하지 않다고 결정하기 위한 좋은 이유일 수 있습니다:
이것은 그만큼 중요하지는 않지만, 실수를 더 빨리 발견하는 데 도움이 됩니다. 우리의 목표는 다른 쪽에 의도되지 않은 코드를 import하는 것에서 비롯된 혼란스러운 런타임 오류—예를 들어 DOM 로직—를 빌드 오류로 바꾸는 것입니다. 이것은 우리가 그것을 수정하도록 강제합니다:
다시, 이것은 우리에게 선택을 제시할 것입니다:
c.js를 리팩토링하여 백엔드에서 작동하도록 할 수 있습니다(그리고 독 알약을 제거합니다).
a.js와 b.js를 리팩토링하여 c.js가 필요하지 않도록 할 수 있습니다.
backend/index.js를 변경하여 a.js와 b.js가 필요하지 않도록 할 수 있습니다.
우리는 client-only와 server-only의 더 세분화된 버전을 더 상상할 수 있습니다. 이것은 개별 패키지 import에 적용됩니다. 예를 들어, React 패키지는 useState와 useEffect와 같은 API를 client-only로 선언할 수 있습니다. 그래서 그들을 백엔드 코드에 끌어들이면 즉시 빌드에 실패합니다. (힌트: React는 실제로 package.json 조건부 내보내기 메커니즘을 통해 그렇게 합니다.) 나는 당신이 여기서 테마를 보기 시작하고 있다고 의심합니다. 백엔드와 프론트엔드 코드베이스 간에 더 많은 코드를 공유하고 재사용하기 시작할 때—그리고 실제로, 이 두 코드베이스가 하나로 혼합될 때—이러한 빌드 타임 어설션은 우리에게 마음의 평화를 줍니다.
모든 모듈이 어떤 쪽에 배타적일 필요는 없습니다. 실제로, 대부분의 모듈은 그렇지 않습니다. 왜냐하면 그들이 비호환성의 원천이 아니기 때문입니다. 예를 들어, a.js와 b.js는 그들이 c.js의 구현 세부사항을 알지 못하기 때문에 한쪽에만 존재해야 한다고 규정하지 않습니다. 하지만 어떤 모듈이 배타적이기를 원한다면, 이제 server-only 또는 client-only로 "로컬하게" 이것을 표현할 수 있습니다. 선언된 비호환성은 그 후 모든 importing 모듈에 "감염"됩니다.
또한 server-only와 client-only "독 알약"이 코드가 어디로 가는지를 제어하지 않는다는 것을 이해하는 것이 중요합니다. 그들이 코드를 "백엔드에 놓거나" "프론트엔드에 놓지" 않습니다. 이러한 어설션이 하는 유일한 것은 코드가 지원되지 않는 환경으로 끌어들여지는 것을 방지하는 것입니다. 그들은 독 알약 뿐입니다.
이 시점에서, 우리는 거의 RSC를 발명했습니다.
남은 마지막 세부사항이 하나 있습니다.
한 프로그램, 두 컴퓨터
우리의 백엔드와 프론트엔드를 별개의 프로그램으로 한 번 더 살펴봅시다:
지금까지, 우리는 이 프로그램들이 코드를 어떻게 공유할 수 있는지에 대한 좋은 정신 모델을 가지고 있습니다:
- 어느 쪽에서든 코드를 import하면 항상 그것을 그 쪽에 가져옵니다.
- 두 모듈 시스템은 완전히 독립적입니다. 공유 코드를 양쪽에서 import하면, 그것은 독립적으로 양쪽에 가져와질 것입니다.
- 기본적으로, 우리는 모든 코드가 재사용 가능하다고 가정합니다. 하지만 우리는
server-only와 client-only 독 알약을 제공합니다. 이것들은 그 모듈 내부의 어떤 코드 때문에 특정 쪽으로 절대 가져와져서는 안 되는 모듈에서 사용되어야 합니다. 이것은 코드가 어디서 실행되는지를 변경하지 않지만, 우리에게 조기 빌드 오류를 줍니다.
정직하게, 우리는 여기서 멈출 수 있고, 우리는 많은 인기 있는 설정보다 더 안전한 코드 재사용을 제공하는 풀스택 개발을 위한 설득력 있는 설정을 가질 것입니다.
그러나, 우리의 접근 방식에는 하나의 남은 약점이 있습니다. 현재, 백엔드 코드와 프론트엔드 코드는 관례에 의존하여 동기화 상태를 유지합니다. 백엔드는 프론트엔드에서 sayHello 함수를 참조하고 싶지만, 구문적으로 그렇게 할 방법이 없어서 다른 쪽에 존재할 것이라고 가정해야 합니다:
이것은 종류의 취약합니다.
물론, 백엔드는 sayHello를 단순히 import할 수 없습니다. 왜냐하면—관찰력 있는 독자가 이미 깨달았을 수 있듯이—그것은 단순히 그것을 백엔드 코드에 가져올 것이기 때문입니다.
백엔드 코드가 그것을 가져오지 않고 다른 세계의 모듈을 참조할 수 있는 어떤 방법이 있으면 좋을 것입니다. 다행히, 그것이 'use client'가 하는 것입니다: 그것이 "남은 10%"입니다. RSC가 추가하는 것입니다.
RSC에서, 양쪽의 import는 일반적으로 일반 import처럼 작동합니다—하지만 'use client'는 이 동작을 "프론트엔드 환경으로의 문을 열기"로 변경합니다.
'use client'를 추가할 때, 당신은 말하고 있습니다: "백엔드 세계에서 나를 import하면, 실제로 내 코드를 백엔드에 가져오지 말고—대신, React가 결국 <script> 태그로 바꾸고 프론트엔드에서 부활시킬 수 있는 참조를 제공하세요."
마찬가지로, 'use server'는 프론트엔드 코드의 일부가 백엔드에 "문을 열고" 백엔드 모듈을 참조할 수 있게 해줍니다. 이것은 그것을 프론트엔드 세계에 가져오지 않고 말입니다. 지시문은 모듈별로 "코드가 어디서 실행되는지"를 지정하기 위한 것이 아닙니다. 모든 프론트엔드 모듈에 'use client'를 넣거나 모든 백엔드 모듈에 'use server'를 넣으면 안 됩니다—그것은 무의미합니다! 그들이 하는 모든 것은 두 모듈 시스템 간에 "문"을 만들 수 있게 해주는 것입니다. 그들은 당신이 다른 세계를 참조할 수 있게 해줍니다.
백엔드에서 프론트엔드로 데이터를 전달하고 싶다면(<script> 태그로), 'use client'가 필요합니다. 프론트엔드에서 백엔드로 데이터를 전달하고 싶다면(API 호출로), 'use server'가 필요합니다. 그렇지 않으면, 지시문이 필요 없습니다—당신은 import를 평소처럼 사용하고 현재 세계에 머물러 있습니다.
결론
RSC는 백엔드와 프론트엔드가 각각 자신의 모듈 시스템을 가지고 있다는 사실을 외면하지 않습니다. 이것은 프론트엔드와 백엔드 간에 일부 코드를 재사용하는 전통적인 JavaScript 코드베이스처럼 정확히 작동합니다. 여기서 재사용된 코드는 효과적으로 양쪽에 존재합니다. RSC가 추가하는 것은 단지 두 가지 메커니즘입니다:
import 'client-only'와 import 'server-only' 독 알약. 이것들은 개별 모듈이 다른 세계로 가져와져서는 안 된다고 선언할 수 있게 해줍니다.
'use client'와 'use server' 지시문. 이것들은 당신이 다른 세계의 모듈을 참조하고 그들에게 데이터를 전달할 수 있게 해줍니다. 그들을 가져오지 않고 말입니다.
이 두 가지 메커니즘으로, 당신은 RSC 애플리케이션을 두 컴퓨터에 걸쳐 있는 단일 프로그램으로 볼 수 있습니다—두 개의 독립적인 모듈 시스템, 두 개의 독 알약, 그리고 그 모듈 시스템 간에 정보를 전달하기 위한 두 개의 문을 가지고 있습니다.
이 "계층화된" 접근 방식이 당신의 근육 기억에 정착하면서, 당신은 frontend/와 backend/ 디렉토리가 불필요하고 심지어 오도할 수 있다는 것을 깨달을 것입니다. 왜냐하면 정보가 이미 모듈에 포함되어 있기 때문입니다. 하지만 그것은 로컬하게 포함되어 있어서 경계가 코드를 진화시키면서 자동으로 이동합니다.
독 알약은 아무것도 잘못된 세계로 가져와지지 않도록 보장하고, 지시문은 세계 간에 정보를 전달할 수 있게 해주고, 일반 import는 평소처럼 작동합니다.
이제 당신이 해야 할 모든 것은 빌드 오류를 수정하는 것입니다.
저는 LLM이 그것에 꽤 능숙해지고 있다고 들었습니다.