https://nextjs.org/docs/advanced-features/dynamic-import
Next에서의 Dynamic Import는 React에서와 매우 유사하므로 간단히 짚고 넘어가도록 하겠습니다. Next.js는 import()문법을 외부 라이브러리로 lazy loading을 지원합니다. 그리고 이는 Next에서 'next/dynamic'패키지에 존재합니다. 이러한 Deferred loading은 초기 화면을 로딩할 떄의 시간향상에 매우 많은 도움을 줍니다.
결과론적으로 컴포넌트를 랜더링하기 위한 다운로드할 Javascript패키지의 크기를 줄이는 것입니다. 그리고 오직 JavaScript 번들 안에서import해서 사용하게끔 해서, 클라이언트 단에서 다운로드 할 수 있게 도와줍니다.
그리고 'next/dynamic'은 React.lazy를 상속하기 떄문에 'Suspense'컴포넌트를 활용하여 hydration과정에서의 fallback도 설정해 줄 수 있습니다.
/components/bs.tsx
console.log("hello iam bs");
export default function Bs() {
return <h1>Hello</h1>;
}
/components/enter.tsx
import { useEffect, useState } from "react";
import type { NextPage } from "next";
import useMutation from "@libs/client/useMutation";
import { cls } from "@libs/client/utils";
import Button from "@components/button";
import Input from "@components/Input";
import { useForm } from "react-hook-form";
import type { SubmitHandler } from "react-hook-form";
import { ConversationList } from "twilio/lib/rest/conversations/v1/service/conversation";
import { useRouter } from "next/router";
import dynamic from "next/dynamic";
// import Bs from "@components/bs";
const Bs = dynamic(() => import("@components/bs"), {
ssr: false,
});
type MethodType = "email" | "phone";
interface EnterForm {
email?: string;
phone?: string;
}
interface TokenForm {
token: string;
}
interface EnterMutationResult {
ok?: boolean;
error?: string;
}
interface MutationResult {
ok?: boolean;
error?: string;
}
const Enter: NextPage = () => {
// 사용자가 로그인하기 위해서 email or phone을 쳐서 token을 발급받는 과정
const [enter, { loading, data, error }] =
useMutation<EnterMutationResult>("/api/users/enter");
// token을 검증하는 과정
const [
confirmToken,
{
loading: tokenLoading,
data: tokenData,
error: tokenError,
},
] = useMutation<MutationResult>("/api/users/confirm");
const [submitting, setSubmitting] = useState(false);
const { register, handleSubmit, reset } = useForm<EnterForm>();
const {
register: tokenRegister,
handleSubmit: tokenHandleSubmit,
} = useForm<TokenForm>();
const [method, setMethod] = useState<MethodType>("email");
const onEmailClick = () => {
reset();
setMethod("email");
};
const onPhoneClick = () => {
reset();
setMethod("phone");
};
const onValid: SubmitHandler<EnterForm> = (validForm) => {
enter(validForm);
};
const onTokenValid = (validForm: TokenForm) => {
if (tokenLoading) return;
confirmToken(validForm);
};
const router = useRouter();
// 만약 tokenData를 검증하는 API로부터 검증하는 요청이 들어왔고
// ok가 true라면 기본페이지로 리다이렉팅 시킨다.
// 반대로 로그인하지 않은 사용자가 / 홈페이지로 들어올려교 하면 /enter로 보내는 작업이 수반되어야 할 것이다.
useEffect(() => {
if (tokenData?.ok) {
router.push("/");
}
}, [tokenData, router]);
return (
<div className="mt-16 px-4">
<h3 className="text-center text-3xl font-bold">
Enter to Carrot
</h3>
<div className="mt-8">
{data?.ok ? (
<form
onSubmit={tokenHandleSubmit(onTokenValid)}
className="mt-8 flex flex-col"
>
<Input
register={tokenRegister("token", {
required: true,
})}
name="token"
label="Confirmation Token"
type="number"
required
/>
<Button
text={tokenLoading ? "Loading" : "Confirm Token"}
/>
</form>
) : (
<>
<div className="flex flex-col items-center">
<h5 className="text-sm font-medium text-gray-500">
Enter using:
</h5>
<div className="mt-8 grid w-full grid-cols-2 gap-16 border-b">
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "email"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onEmailClick}
>
Email
</button>
<button
className={cls(
"border-b-2 pb-4 font-medium",
method === "phone"
? " border-orange-500 text-orange-500"
: "border-transparent text-gray-500"
)}
onClick={onPhoneClick}
>
Phone
</button>
</div>
</div>
<form
onSubmit={handleSubmit(onValid)}
className="mt-8 flex flex-col"
>
{method === "email" ? (
<Input
register={register("email", {
required: true,
})}
name="email"
label="Email address"
type="email"
/>
) : null}
{method === "phone" ? (
<>
<Bs />
<Input
register={register("phone", {
required: true,
})}
name="phone"
label="Phone number"
type="number"
kind="phone"
/>
</>
) : null}
{method === "email" ? (
<Button
text={loading ? "Loading" : "Get login link"}
/>
) : null}
{method === "phone" ? (
<Button
text={
loading ? "Loading" : "Get one-time password"
}
/>
) : null}
</form>
</>
)}
<div className="mt-8">
<div className="relative">
<div className="absolute w-full border-t border-gray-300" />
<div className="relative -top-3 text-center ">
<span className="bg-white px-2 text-sm text-gray-500">
Or enter with
</span>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-3">
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M6.29 18.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0020 3.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.073 4.073 0 01.8 7.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 010 16.407a11.616 11.616 0 006.29 1.84" />
</svg>
</button>
<button className="flex justify-center rounded-md border border-gray-500 bg-white py-2 px-4 text-sm font-medium text-gray-500 shadow-sm hover:bg-gray-50">
<svg
className="h-5 w-5"
aria-hidden="true"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
);
};
export default Enter;
이렇게 enter페이지를 예전에 구축했었습니다. 그런데, Phone탭을 클릭하면 bs컴포넌트를 렌더링하게 했습니다. 원래라면 최 상단에 bs컴포넌트를 불러왔으므로 console.log가 javascript번들을 브라우저에서 다운로드 받을 때, 즉 enter페이지에 접근하자마자 바로 찍혀야 합니다. 다만 우리는 dynamic import를 활용해씅므로 Phone탭을 클릭해서 bs컴포넌트가 보여지는 경우에만 lazy loading을 해서 console.log가 찍히게 했습니다.
다음과 같이 Phone탭을 클릭하면 webpack에서 chunk를 다운로드 받아서 화면에서 lazy loading을 하는것을 보실 수 있습니다.
그 외에도 옵션을 주어서 Server Side에서 Import하지 않게도 설정할 수 있습니다. SSR을 위해서 만약에 browser API인 'window'객체를 활용하는 코드를 다운받는다면 이는 오류가 나게 됩니다. 그래서 ssr: false를 주어 이러한 오류를 미연에 방지할 수 있게 할 수 있습니다.
'Web > NextJs' 카테고리의 다른 글
[ Next.js - DeepDive ] - _document and Fonts (0) | 2022.08.04 |
---|---|
[ Next.js - DeepDive ] - Lazy-load Imports (0) | 2022.08.04 |
[ Next.js - DeepDive ] - Responses and Redirections (0) | 2022.08.04 |
[ Next.js - DeepDive ] -미들웨어 ( Middlewares ) (0) | 2022.08.04 |
Next.js - _document와 _app에 대하여 (0) | 2022.03.30 |