Remix에는 다양한 모듈을 지원하고 있는데 그중에 몇가지에 대해서 정리하고자 한다.
첫번째로 links 모듈에 대해서 설명하고자하는데 이는 HTML 요소중에 <link>태그를 추가하는 용도로 사용한다. 예를 들어 icon, StyleSheet, /img/vercel.svg을 <link>태그를 이용해 넣을때는 아래처럼 넣는다.
export const links: LinksFunction = () => {
return [
{rel:"icon", href: "/favicon.ico", type: "image/png"},
{
rel: "stylesheet",
href: StyleSheet,
},
{
rel: "prefetch",
as: "image",
href: "/img/vercel.svg"
}
]
}
먼저 결과를 보기전에 links 모듈을 넣기전에 HTML을 보면 아래와 같다.

links 모듈을 넣고난 후의 결과는 아래와 같다. 아래에 icon, stylesheet와 prefetch가 포함된 3개의 <link>태그가 추가된것을 확인할 수 있다.

추가로 네트워크 탭에서 이미지를 prefetch도 정상적으로 불러와지는 것을 확인할 수 있는데 /img/vercel.svg파일이 없기때문에 에러가 발생하는데 호출이 정상적으로 이루어진다만 확인하면 된다.

💡link모듈을 사용하면 파일 뿐만 아니라 페이지 자체로 prefetch를 할 수 있는데,
<Link prefetch="render">방식이 있기 때문에 사용하지 않는것을 추천한다고 한다. 그 이유는 link모듈을 사용해서 페이지를 prefetch하게 되면 하위 페이지에 있는 자바스크립트와 데이터를 모두 불러오기 때문에 사용자가 페이지에 접속하지 않음에도 불구하고 페이지에 대한 정보를 미리 불러와 속도가 느려지거나 과도한 트래픽을 유발할 수 있다고 한다.
<Link/>태그의 prefetch 속성에 대해서 실습 및 차이점을 확인해보았다.
// dynamic/route.tsx
export default function Dynamic() {
return (
<div style={{ border: "3px solid red" }}>
<h1>Dynamic</h1>
<div className="space-x-5">
<Link to="/dynamic/1" prefetch="intent">
게시판1
</Link>
<Link to="/dynamic/2" prefetch="render">
게시판2
</Link>
<Link to="/dynamic/3" prefetch="none">
게시판3
</Link>
</div>
<Outlet />
</div>
);
}
// dynamic.$id/route.tsx
export default function Depth1() {
const { id } = useParams();
return (
<div style={{ border: "3px solid green" }}>
<h1>게시판 {id}</h1>
<Outlet />
</div>
);
}
이렇게 두개의 라우터를 이용해서 실습을 진행해보았는데 결과는 다음과 같다.
intent
마우스 hover 또는 focus할때 지정된 링크 페이지를 미리 호출한다.

render
사실 아래 영상을 봤을때는 어떤 느낌인지 모르겠어서 찾아보았는데 render 같은 경우에는 DOM요소에 Link태그가 보이는(생성되는) 순간 실행된다고 한다.

none
별도로 프리패치를 하지 않고 클릭하는 순간 요청을 한다.

links모듈과 마찬가지로 HTML요소의 <meta>태그를 쉽게 사용할 수 있게 해주는 모듈로 일반적으로 <meta>태그를 이용해서 SEO 처리를 해주는데 아래와 같이 코드를 작성하면 된다.
export const meta: MetaFunction = () => {
return [
{ title: "메인페이지입니다." },
{ name: "description", content: "페이지 확인을 위한것!" },
];
};
브라우저에서 HTNL요소를 확인해보면 정상적으로 <meta>태그가 들어간것을 확인할 수 있다.

각 Route마다 HTTP 헤더를 정의할때 사용한다고 한다. 일반적으로 캐싱제어를 할때 사용함, API 응답이나 페이지 데이터의 캐시 기간을 설정할 때 사용한다.
export const headers: HeadersFunction = ({
loaderHeaders,
actionHeaders,
parentHeaders,
}) => ({
"Cache-Control": "max-age=369, s-maxage=3636"
})
결과를 보면 페이지를 호출할때 Cache-Control값에 정상적으로 값이 들어간것을 확인할 수 있다.

여기서 조금 의문인게 react-query를 사용하면 캐시처리가 자동으로 되는게 아닌가라는 의문을 가지고 Claude한테 질문을 하였다. 질문의 내용은 캐시처리는 react-query를 쓰면 되는거 아니야?라고 질문을 하게되었는데 답변은 다음과 같았다.
두가지는 서로 다른 레벨에서 동작하는 캐싱이며 차이점은 아래와 같다.
우선 React-query에 대한 내용은 React와 Next를 개발하면서 경험한것이라서 이해가 되었는데 Remix의 3번 항목인 여러 사용자가 공유할 수 있는 캐시(CDN)부분이 이해가 가지 않았다.
예를들어 사용자 A가 먼저 블로그 포스트에 접속하고 사용자 B가 그 이후에 블로그 포스트에 접속하는 경우를 이용해 설명해보면.
"Cache-Control: public, max-age=3600"즉 동일한 페이지를 접속하는경우 첫사용자만 접속을 하면 요청의 수를 줄일 수 있다는 것을 확인할 수 있다. CDN이 없는 경우에는 브라우저 캐시를 사용하므로 동일한 브라우저에서 첫번째 접속할때만 네트워크 요청을하면 추가 요청은 없게된다.
이러한 특성때문에 캐시는 public(블로그 포스트, 상품 목록, 공지사항)과 같이 모든 사용자가 동일한 응답을 받아도 되는 경우에 사용하여 시스템 효율성을 높이는 목적으로 사용한다고 한다.
어쩌다보니 header모듈에서 사설이 많이 길었는데 loader 모듈에 대해 알아보자!
Route에 접근하여 페이지가 렌더링 될때 Remix Server에서 실행되는 함수이며, 주로 첫 페이지 접근 시 필요한 데이터를 클라이언트 단으로 보낼때 사용한다. loader함수의 경우 서버단에서 실행되기 때문에 Prisma와 같은 ORM, Redis와 같은 캐시 프로그램과 연계하여 사용한다.
Nextjs의 getServerSideProps 함수와 동일한 기능을 한다고 보면 된다.
사용방법은 아래와 같다.
export const loader: LoaderFunction = async({ request, params }): Promise<TLoaderData> => {
console.log("이 로그는 브라우저가 아닌 터미널에서 나옴");
// Request에 있는 Cookie 가져오는 법
const cookie = request.headers.get("Cookie");
// URL Query query string 가져오기
const url = new URL(request.url);
const query = url.searchParams.get("query");
const id = patams.id;
console.log("Cookie", cookie);
console.log("URL", url);
console.log("Query", query);
console.log("id", id);
return {
status: 200,
message: "Hello World"
};
}
http://localhost:5173/loader-and-action?query=dd로 접속하면 콘솔창에는 아래와 같이 표시 된다. id의 경우 나중에 추가한거라 표시가 안되는데 어쨋든 나온다. 눈으로만 보지말고 직접 해보세요!

실제로 컴포넌트에서 사용할때는 useLoaderData함수를 통해 가져와서 ErrorBoundary모듈을 통해 에러 처리도 가능하기 때문에 Nextjs의 getServerSideProps보다 더 안정한 방식으로 구현되었다고 볼 수 있다고 한다. 브라우저에서 가져오는 방법은 다음과 같다
export default function LoaderAndAction() {
const data = useLoaderData<typeof loader>();
console.log(data);
return <div>test</div>
}

action함수는 loader와 동일하게 Remix Server에서만 실행되는 함수로 action함수가 있는 Route에 HTTP Method로 GET이 아닌 POST, PUT, PATCH, DELETE로 요청이 오게되면 loader함수보다 먼저 실행이 된다.
loader함수와의 차이점은 실행되는 시점이다.
export const action = async ({ request }: ActionFunctionArgs) => {
console.log("Action 실행");
const body = await request.formData();
const name = body.get("name");
console.log("name :", name);
return redirect("/loader-and-action");
};
export default function LoaderAndAction() {
const initalData = useLoaderData();
return (
<div>
{JSON.stringify(initalData)}
<Form method="post">
<input type="text" name="name" />
<button type="submit">전송</button>
</Form>
</div>
);
}
브라우저의 input박스에 "안녕하세요"를 입력하고 전송 버튼을 누르게되면 콘솔창에 다음과 같이 표시된다.


이런식으로 Remix Server쪽에서 DB의 값을 새로 생성하거나 갱신할때 사용하는 Mutation작업을 진행할 수 있다.
정리하면서 궁금한 점들이 계속 생각나는데 우선 action과 loader를 사용하면 코드가 훨씬 깔끔해지는 효과도 있겠다 싶었다.

https://remix.run/docs/en/main/route/links
https://fastcampus.co.kr/dev_online_remix