
익스플로러 10(8회), 11(13회)을 제외하고 익스플로러 8, 9, 크롬, 사파리, 파이어폭스, 오페라, 안드로이드, iOS는 모두 HTTP 요청을 한 번에 6회까지 밖에 하지 못한다. 즉 리소스를 한번에 6개씩 다운로드 받아올 수 있다는 말이다. 요청 횟수가 제한되어 있다면 바로 보여줘야하는 리소스들을 먼저 다운로드 받는 것이 사용자 경험에 좋을 것이다.
preload는 현재 페이지에서 쓸 리소스들을 모두 다운로드 받아놓는 것이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<title>프리로드</title>
<link rel="preload" as="image" href="./apple.jpeg" />
<!-- 일반로드 -->
<link rel="stylesheet" href="./index.css" />
<script src="index1.js"></script>
<script src="index2.js"></script>
<script src="index3.js"></script>
<script src="index4.js"></script>
<script src="index5.js"></script>
<script src="index6.js"></script>
</head>
<body>
<img src="./apple.jpeg" />
</body>
</html>
이미지 링크에 preload 속성을 걸어서 이벤트에 실행될 javascript 파일들보다 먼저 다운로드 받아와 화면로드 지연을 방지할 수 있다.
preload로 페이지 이동 전 이동할 페이지에서 필요한 이미지를 미리 받아올 수 있다. 이 땐 이미지를 담을 변수를 전역변수로 선언해야한다. 이미지가 프리로드 되면 화면이동 후 불러온 함수가 삭제되어도 이미지는 캐시에 저장되기 때문이다. 이미지 이름이 같다면 같은 이미지로 인식된다.
const PRELOADED_IMAGES = [];
export default function PreloadPage () {
const router = useRouter()
const divRef = useRef(null)
useEffect(() => {
const preloadImage = () => {
const img = new Image()
img.src = PRELOAD_IMAGES
img.onload = () => {.
PRELOADED_IMAGES.push(img)
}
}
preloadImage();
}, []);
const onClickPreload = () => {
if(imgTag) divRef.current?.appendChild(imgTag)
}
return (
<div>
<div ref={divRef}></div>
<button onClick={onClickPreload}> 이미지 프리로드 </button>
</div>
)
}
🚨
preload로 이미지를 캐싱하면 강제로 삭제하지 않는 이상 계속 캐시메모리에 유지된다. 많은 이미지를 미리 불러와놓는다면 메모리 누수가 일어나게 된다. 꼭 필요한 이미지들만preload하는 것에 주의한다.
prefetch는 다음 페이지에서 쓰기 위한 리소스를 미리 다운 받는 것을 말한다. 현재 페이지에 필요한 리소스들을 모두 다운 받은 후 가장 마지막에 다운로드하게 된다.
<!-- index.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>프리페치</title>
<link rel="prefetch" href="board.html" />
</head>
<body>
<a href="board.html">게시판으로 이동하기</a>
</body>
</html>
이동할 페이지를 prefetch 속성으로 미리 다운받아놓아 페이지 이동 시 로딩 없이 바로 보여줄 수 있다.
<!-- board.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<title>게시판</title>
</head>
<body>
여기는 게시판입니다
</body>
</html>
페이지 이동 전 마우스를 호버했을 때 다음 페이지에서 사용할 데이터를 미리 받아와둘 수 있다. 받아진 데이터는 아폴로 캐시에 저장된다.
import { gql, useApolloClient, useQuery } from "@apollo/client";
import {
IQuery,
IQueryFetchBoardsArgs,
} from "../../../src/commons/types/generated/types";
import { wrapAsync } from "../../../src/commons/libraries/asyncFunc";
import { useRouter } from "next/router";
import _ from "lodash";
const FETCH_BOARDS = gql`
query fetchBoards($page: Int) {
fetchBoards(page: $page) {
_id
writer
title
contents
}
}
`;
const FETCH_BOARD = gql`
query fetchBoard($boardId: ID!) {
fetchBoard(boardId: $boardId) {
_id
writer
title
contents
}
}
`;
export default function PaginationPage(): JSX.Element {
const router = useRouter();
const { data } = useQuery<Pick<IQuery, "fetchBoards">, IQueryFetchBoardsArgs>(
FETCH_BOARDS,
);
const client = useApolloClient();
const getDebounce = _.debounce((boardId) => {
void client.query({
query: FETCH_BOARD,
variables: { boardId },
});
}, 300);
const prefetchBoard = (boardId: string) => async () => {
getDebounce(boardId);
};
const onClickMove = (boardId: string): void => {
void router.push(`이동할 주소`);
};
return (
<div>
{data?.fetchBoards.map((item) => (
<div key={item._id}>
<span>
<input type="checkbox" />
</span>
<span style={{ margin: "10px" }}>{item._id}</span>
<span
style={{ margin: "10px" }}
onMouseOver={prefetchBoard(item._id)}
onClick={onClickMove}
>
{item.title}
</span>
<span style={{ margin: "10px" }}>{item.writer}</span>
</div>
))}
</div>
);
}
마우스가 호버될 때마다 요청을 보내면 성능 저하가 일어나기 때문에 debounce를 걸어서 0.3초 이상 머무른 대상에 대해서만 요청을 하도록 한다.
페이지를 읽는 시점에 중요하지 않은 리소스의 로딩을 나중에 하는 기술이다. 스크롤이 내려가면서 필요한 때에 로드가 일어난다. 페이지에 진입해서 화면에 보이는 이미지만 미리 로드하고 스크롤을 내려야만 보이는 이미지는 lazyload를 이용해 나중에 로드하는 것이 좋다.
react-lazyload, react-lazy-load 등의 라이브러리로 쉽게 구현할 수 있다.