일단 nextjs의 관련된 걸 적어두고, 나중에 세부 게시물로 더 자세히 써보자!
ssr 관련된 기술은 따로 정리하기.
https://leetrue-log.vercel.app/nextjs-12-rendering
정리된 블로그도 다시 정독하고 참고하자!
import Image from "next/image"; 로 사용하는 Image 컴포넌트의 특징을 알아보자.
성능 최적화: 최적화 기능을 다양하게 제공한다. 이미지 리사이징, 최적화, 브라우저별 최적화 등을 자동으로 처리해 페이지 로딩 속도를 향상시킨다.
레이아웃 지원: layout 속성으로 이미지 렌더링의 방식을 지정할 수 있다.
layout="intrinsic"이 기본값. 이미지의 원래 크기.
responsive는 부모에 맞춤. 뷰포트 크기에 따라 달라짐,
fixed
fill 이 있다.
lazy loading 지원: SSR과 함께 사용, 서버에서 이미지를 최적화하며 페이지 렌더링 속도 향상. (모양새를 미리 보여주기에 로딩 화면이 좀 더 낫다.)
참고 할 블로그!
Next Image 컴포넌트 가이드
Next/Image를 활용한 이미지 최적화
api/users/me/index.ts 에서 req.session을 확인함libs/client/useUSer.ts 에서 api/users/me을 useSWR로 데이터를 가져옴export default function useUser() {
const { data, error } = useSWR<ProfileResponse>("/api/users/me");
const router = useRouter();
useEffect(() => {
if (data && !data.ok) {
router.replace("/login");
}
}, [data, router]);
return { user: data?.profile, isLoading: !data && !error };
}
middleware.tsuseUser는 실제로 DB에 요청을 보내서 응답을 받아서 확인한다. 미들웨어로 단순하게 유저의 쿠키 여부를 본다면 쿠키가 유효기간이 지났거나 변형됐거나 하는 가능성이 있음!
export function middleware(req: NextRequest, ev: NextFetchEvent) {
console.log(req.ua);
if (req.ua?.isBot) {
return new Response("봇은 안돼요", { status: 403 });
} //status도 추가 가능.
if (!req.url.includes("/api")) {
if (!req.url.includes("/enter") && !req.cookies.carrotsession) {
return NextResponse.redirect("/enter");
}
}
}
export const middleware = async (req: NextRequest, ev: NextFetchEvent) => {
const res = NextResponse.next();
const session = await getIronSession(req, res, {
cookieName: "carrotsession",
password: process.env.COOKIE_PASSWORD!,
cookieOptions: {
secure: process.env.NODE_ENV! === "production", // if you are using https
},
});
if (!req.url.includes("/api")) {
return;
if (!session.user && !req.url.includes("/login")) {
const url = req.nextUrl.clone();
url.pathname = "/login";
return NextResponse.rewrite(url);
}
}
};
https://nextjs.org/docs/pages/building-your-application/routing/middleware
https://nextjs.org/docs/messages/nested-middleware
import dynamic form "next/dynamic"
const Bs = dynamic(() => import("@components/bs"),
{ ssr: false, loading: () => <div>loading ... </div> });
//사용예시
return (
<>
<Bs />
</>
)
return (
<>
<Suspense fallback="로딩 중">
<Bs />
</Suspense>
</>
)
import Document, { Head, Html, Main, NextScript } from "next/document";
class CustomDocument extends Document {
render(): JSX.Element {
console.log("DOCUMENT IS RUNNING");
return (
<Html lang="ko">
<Head>
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default CustomDocument;
beforeInteractive: 페이지가 interactive 되기 전에 로드
afterInteractive: (기본값) 페이지 먼저 불러온 뒤에 스크립트 로드
lazyOnload: 다른 모든 데이터나 소스를 불러온 후에 로드
worker: (실험적인) web worker에 로드
import Script from 'next/script'
< Script src="https://connect.facebook.net/en_US/sdk.js" strategy="lazyOnload" / >
export async function getServerSideProps() {
const products = await client.product.findMany({});
return {
props: {
products: JSON.parse(JSON.stringify(products)),
},
};
}
// export default하는 함수에게 getServerSideProps가 요청하는 데이터 전달
SWR 편리한 기능: 캐시에 있는 데이터 변경, swr이 자동으로 데이터를 갱신해최신 데이터로 볼 수 있음
getServerSideProps(ctx: NextPageContext)SWRConfig 태그로 원하는 페이지 컴포넌트를 감싼 함수를 새로 만든다.fallback props에 api 키값을 넣어서 캐시의 초기값을 지정getServerSideProps 함수에서 prisma의 client 코드 작성 (기존 api 폴더에 있는 내용을 옮긴다)즉 원하는 사이트의 렌더링을 서버단에서 미리 설정해두고, useSWR의 fallback을 이용해서 컴포넌트의 캐시 초기값을 설정한다.
const Page: NextPage<{ products: ProductWithCount[] }> = ({ products }) => {
return (
<SWRConfig
value={{
fallback: {
"/api/products": {
ok: true,
products,
},
},
}}
>
<Home />
</SWRConfig>
);
};
export default Page;
revalidate 옵션에 시간 설정을 해줌.revalidate는 특정 페이지, 동작에서도 사용 가능(포스트 생성api)getStatcitProps로 정적 페이지의 데이터를 가져올 때, 동적 url의 사이트에서는 getStaticPaths도 함께 사용한다. 빌드 타임에 어떠한 페이지들이 만들어져야하는지 nextjs에게 알려줘야 함.
정적 생성해야하는 경로들의 배열. 동적 경로를 어떻게 생성할지 정보를 제공. 빈 배열임은 동적 경로가 아직 정의되지 않았음을 의미.
1) blocking: 요청된 경로가 정적으로 생성되지 않은 경우 nextJs는 서버에서 페이지를 생성하고 반환한다. (true와 백그라운드 작업이 같음)
2) false: 정적으로 생성되지 않은 경로는 404 페이지로 처리
3) true: 요청 경로가 정적으로 생성되지 않은 경우 빈 페이지 반환,클라이언트 측에서 데이터를 가져와서 업데이트. 로딩 화면을 보여줄 수 있는 차이점!
api 핸들러에서 data 받아오던 것들을 getStaticProps로 바꿀 수 있다.
📂pages/products/[id].tsx
// api 핸들러에서 return해준 res.json을
res.json({
ok: true,
product,
relatedProducts,
isLiked,
});
----------------------
// 동적 url이기에 같이 써주는 paths.
export const getStaticPaths: GetStaticPaths = () => {
return {
paths: [], // 빈배열로 실제 값을 주진 않았음.
fallback: "blocking", // 사용자가 요청시 생성함
};
};
// getStaticProps에서 props로 리턴해준다. api ts파일의 코드도 옮긴다고 가정.
export const getStaticProps: GetStaticProps = async (ctx) => {
return {
props: {
product: JSON.parse(JSON.stringify(product)),
relatedProducts: JSON.parse(JSON.stringify(relatedProducts)),
isLiked,
},
};
}
// fallback: true 일 때 로딩 ui 보여주기
📂pages/propducts/[id].tsx
if (router.isFallback) {
return(<span>로딩중</span>)
}
export async function getStaticProps() {
const posts = await client.post.findMany({ include: { user: true } });
return {
props: {
posts: JSON.parse(JSON.stringify(posts)),
},
revalidate: 10,
};
}
async function handler(
req: NextApiRequest,
res: NextApiResponse<ResponseType>
) {
if (req.method === "POST") {
const {
body: { question },
session: { user },
} = req;
const post = await client.post.create({
data: {
question,
user: {
connect: {
id: user?.id,
},
},
},
});
await res.revalidate("/community");
res.json({
ok: true,
post,
});
}