티스토리 뷰
💛 .client modules
흔하지는 않지만 브라우저에서 모듈 부작용을 사용하는 파일이나 종속성이 있을 수 있다.
서버 번들에서 강제로 제외하기 위해 파일 이름에 *.client.ts를 사용하거나 .client 디렉토리 내에 파일을 중첩할 수 있다.
// feature-check.client.ts
export const supportsVibrationAPI =
"vibrate" in window.navigator;
이 모듈에서 내보낸 값은 서버에 있으므로 모두 undefined가 될 것이기 때문에 이를 사용할 수 있는 유일한 장소는 useEffect와 클릭 핸들러와 같은 유저 이벤트이다.
import { supportsVibrationAPI } from "./feature-check.client.ts";
console.log(supportsVibrationAPI);
// server: undefined
// client: true | false
.client 디렉토리는 Remix Vite를 사용할 때만 지원된다.
Classic Remix Compiler는 오직 .client 파일만 지원한다.
💛 .server modules
꼭 필요한 것은 아니지만 .server 모듈은 전체 모듈을 서버-전용으로 명시적으로 표시하는 좋은 방법이다.
.server 파일이나 .server 디렉토리의 코드가 실수로 클라이언트 모듈 그래프에 표시되면 빌드가 실패한다.
app
├── .server 👈 이 디렉토리 안의 모든 파일은 서버-온리
│ ├── auth.ts
│ └── db.ts
├── cms.server.ts 👈 이 파일은 서버-온리
├── root.tsx
└── routes
└── _index.tsx
.server모듈은 Remix app 디렉토리 내에 있어야 한다.
.server 디렉토리는 Remix Vite를 사용할 때만 지원된다.
Classic Remix Complier는 .server 파일만 지원한다.
Classic Remix Complier를 사용할 때 .server 모듈은 빈 모듈로 대체되며 컴파일 오류가 발생하지 않는다. 이로 인해 런타임 오류가 발생할 수 있다.
💛 Assets Imports
App 폴더 내의 모든 파일은 모듈로 가져올 수 있다.
- 파일을 브라우저 빌드 디렉토리에 복사한다.
- 장기 캐싱을 위해 파일을 확인(핑거프린트)
- 렌더링하는 동안 사용할 모듈에 공개 URL을 반환한다.
이는 스타일시트에 가장 일반적이지만 정의된 loader가 있는 모든 파일 유형에 사용할 수 있다.
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import banner from "./images/banner.jpg";
import styles from "./styles/app.css";
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: styles },
];
export default function Page() {
return (
<div>
<h1>Some Page</h1>
<img src={banner} />
</div>
);
}
💛 entry.client
기본적으로 Remix는 클라이언트에서 앱 수화를 처리한다.
이 동작을 커스터마이즈하려면, npx remix reveal을 실행해 우선적으로 처리되는 app/entry.client.tsx(또는 .jsx)를 생성할 수 있다.
이 파일은 브라우저의 진입점이며 server entry module에서 서버가 생성한 마크업을 수화하는 역할을 한다. 그러나 여기에서 다른 클라이언트 측 코드를 초기화할 수도 있다.
일반적으로 이 모듈은 server entry module에서 이미 서버에서 생성된 마크업을 수하하기 위해 ReactDOM.hydrateRoot를 사용한다.
// app/entry.client.tsx
import { RemixBrowser } from "@remix-run/react";
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>
);
});
이것은 브라우저에서 실행되는 첫 번째 코드 조각이다. 클라이언트 측 라이브러리를 초기화하고 클라이언트 전용 공급자를 추가하는 등의 작업을 수행할 수 있다.
💛 entry.server
기본적으로 Remix는 HTTP 응답 생성을 처리한다.
이 동작을 커스터마이즈하려면, npx remix reveal을 실행해 우선적으로 처리되는 app/entry.server.tsx(또는 .jsx)를 생성할 수 있다.
이 모듈의 default export는 HTTP 상태, 헤더 및 HTML을 포함한 응답을 생성할 수 있는 함수이고, 마크업이 생성되어 클라이언트에 전송되는 방식을 완전히 제어할 수 있다.
이 모듈은 현재 요청에 대한 컨텍스트와 url을 가지고 <RemixServer> 요소를 사용해 현재 페이지에 대한 마크럽을 렌더링해야 한다.
이 마크업은 브라우저 입력 모듈을 사용하여 브라우저에 javaScript가 로드되면 (선택적으로)다시 수화된다.
- handleDataRequest
데이터 요청의 응답을 수정할 수 있도록 할 수 있는 선택적인 handleDataRequest 함수를 내보낼 수 있다.
이는 HTML을 렌더링하지 않고 클라이언트 측 하이드레이션이 발생한 후 로더 및 action 데이터를 브라우저에 반환하는 요청이다.
export function handleDataRequest(
response: Response,
{
request,
params,
context,
}: LoaderFunctionArgs | ActionFunctionArgs
) {
response.headers.set("X-Custom-Header", "value");
return response;
}
- handleError
기본적으로 Remix는 마주한 서버 측 오류를 콘솔에 기록한다.
기록을 더 강력하게 제어하고 싶거나, 이러한 오류를 외부 서비스에 보고히려는 경우에는 제어권을 부여하고 내장된 오류 로깅을 비활성화하는 선택적인 handleError 함수를 내보낼 수 있다.
export function handleError(
error: unknown,
{
request,
params,
context,
}: LoaderFunctionArgs | ActionFunctionArgs
) {
if (!request.signal.aborted) {
sendErrorToErrorReportingService(error);
console.error(formatErrorForJsonLogging(error));
}
}
Remix의 취소 및 경쟁 상태(race-condition) 처리로 인해 많은 요청이 중단될 수 있으므로, 일반적으로 요청이 중단될 때 로깅을 피하고 싶어한다는 것을 알아두어라!
▪︎ Streaming Rendering Error
renderToPipeableStream 또는 renderToReadableStream을 통해 HTML 응답을 스트리밍할 때, 자체 handleError 구현에서는 초기 셀 렌더링 중에 발생한 오류만 처리한다. 후속 스트리밍 렌더링 중에 렌더링 오류가 발생하는 경우 Remix 서버가 해당 시점까지 이미 응답을 보냈으므로 이러한 오류를 수동으로 처리해야 한다.
- renderToPipeableStream의 경우, onError 콜백 함수에서 이러한 에러를 처리할 수 있다. onShellReady에서 오류가 셸 렌더링 오류(무시 가능)인지 아니면 비동기 렌더링 오류(처리해야만 함)인지 알 수 있도록 불리언을 토글해야한다.
- 예시) Node를 위한 entry.server.tsx 기본값 참조
- renderToReadableStream의 경우, onError 콜백 함수에서 이러한 에러를 처리할 수 있다.
- 예시) Cloudflare를 위한 entry.server.tsx 기본값 참조
▪︎ Thrown Responses
handleError는 loader/action 함수로부터 발생한 Response 인스턴스를 처리하지 않는다는 것을 알아두어라!
이 핸들러의 목적은 코드에서 예상치 못한 오류가 발생한 버그를 찾는 것이다. 시나리오를 감지하고 loader/action의 401/404/등 Response를 던지는 경우 이것은 코드에서 처리되는 예상 흐름이다. 로그에 기록을 하거나 외부 서비스로 보내길 원한다면, 응답을 던질 때 수행해야 한다.
💛 Root Route
루트 라우트(app/root.tsx)는 routes/directory에 있는 모든 라우트의 상위 경로이고 루트 <html> 문서 렌더링을 담당하기 때문에 Remix 애플리케이션에서 유일하게 필요한 경로이다.
루트 라우트는 문서를 관리하기 때문에 Remix가 제공하는 소수의 "문서 수준" 컴포넌트를 렌더링하기에 적합한 위치이다.
이러한 구성 요소는 루트 경로 내에서 한 번 사용되며 페이지가 제대로 렌더링되도록 Remix가 파악하거나 구축한 모든 것을 포함한다.
// app/root.tsx
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import globalStylesheetUrl from "./global-styles.css";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: globalStylesheetUrl }];
};
export default function App() {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
{/* 모든 라우트에 있는 meta 내보내기가 여기에서 렌더링됨 */}
<Meta />
{/* 모든 라우트에 있는 link 내보내기가 여기에서 렌더링됨 */}
<Links />
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
}
- Layout Export
루트 라우트는 모든 경로에 대한 문서를 관리하므로 추가적인 선택적 Layout 내보내기도 지원한다.
레이아웃 라우트는 두 가지 목적으로 사용된다.
- root 컴포넌트, HydrateFallback, ErrorBoundary에서 문서'/app shell'을 복제하는 것을 피하기
- React가 <Links> 컴포넌트에서 <link rel="stylesheet"> 태그를 제거하고 다시 추가하는 경우 FOUC가 발생할 수 있는 root 컴포넌트/HydrateFallback/ErrorBoundary 사이를 전환할 때 React가 app shell 요소를 다시 마운트하는 것을 피하기
Layout 컴포넌트의 useLoaderData에 대한 참고 사항
useLoaderData는 happy-path 라우트 렌더링을 위한 것이므로 ErrorBoundary 컴포넌트에서 사용할 수 없으며, 해당 입력에는 로더가 성공적으로 실행되어 무언가를 반환했다는 가정이 내장되어 있다. 그 가정은 경계를 던지고 촉발한 loader일 수 있기 때문에 ErrorBoundary에 적용되지 않는다! ErrorBoundary의 loader 데이터에 접근하는 대신, loader 데이터가 잠재적으로 undefined로 설명하는 useRouteLoaderData를 사용할 수 있다.
Layout 컴포넌트가 성공 및 오류 플로우 모두에서 사용되기 때문에, 이와 동일한 제한 사항이 적용된다.
성공적인 요청인지 여부에 따라 Layout에서 로직을 분기해야하는 경우에는 useRouteLoaderData("root")와 useRouteError()를 사용할 수 있다.
// app/root.tsx
export function Layout({
children,
}: {
children: React.ReactNode;
}) {
const data = useRouteLoaderData("root");
const error = useRouteError();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<Meta />
<Links />
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--themeVar: ${
data?.themeVar || defaultThemeVar
}
}
`,
}}
/>
</head>
<body>
{data ? (
<Analytics token={data.analyticsToken} />
) : null}
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
'코딩 > 코딩노트' 카테고리의 다른 글
[Remix]Remix 공식문서 파헤치기 6탄 - 컴포넌트 (0) | 2024.04.26 |
---|---|
[Remix]Remix 공식문서 파헤치기 5탄 - Hooks (0) | 2024.04.23 |
[Remix]Remix 공식문서 파헤치기 3탄 - Form vs fetcher (0) | 2024.04.19 |
[Remix]Remix 공식문서 파헤치기 2탄 - 데이터의 흐름과 상태 관리 (0) | 2024.04.15 |
[Remix] Remix 공식 문서 파헤치기 1탄 - Remix가 뭔데?!(route 집중 파보기) (0) | 2024.04.11 |