โœ๐Ÿป [Code Camp_TIL] 33์ผ์ฐจ: ์ด๋ฏธ์ง€ ์„ฑ๋Šฅ ๊ฐœ์„ (์ž„์‹œ URL, Promise.all(), LazyLoad vs PreLoad, Prefetch, Webp)

code_Jยท2023๋…„ 5์›” 1์ผ
0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
40/41
post-thumbnail

์ด๋ฏธ์ง€ ์„ฑ๋Šฅ ๋†’์ด๊ธฐ


๊ธฐ์กด ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ์—์„œ์˜ ๋ฌธ์ œ์ 

  1. ๋‹ค๋ฅธ ์‚ฌ๋žŒ ์ปดํ“จํ„ฐ์— ํ•ด๋‹น ์ด๋ฏธ์ง€๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
  2. ์—…๋กœ๋“œํ•˜๋‹ค๊ฐ€ ์ค‘๋‹จ ์‹œ์— ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ง€์›Œ์ง€์ง€ ์•Š๊ณ  ๋‚จ์Œ

์œ„์™€ ๊ฐ™์€ ๋ฌธ์ œ์ ์€ ์ด๋ฏธ์ง€ ์ฃผ์†Œ๋ฅผ ์„œ๋ฒ„์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์šฉ์œผ๋กœ ๋งŒ๋“  ์ž„์‹œ ์ฃผ์†Œ๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ž„์‹œ ์ฃผ์†Œ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ ‘์† ๊ฐ€๋Šฅํ•œ ์ž„์‹œ URL์ด๋‹ค.


์ž„์‹œ URL ๋ฐ›์•„์˜ค๊ธฐ

์ž„์‹œ URL์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์€ 2๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค.

  1. ๊ฐ€์งœ URL ์ƒ์„ฑํ•˜๊ธฐ(๋‚ด ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์ด๋ฏธ์ง€์— ์ ‘๊ทผ ๊ฐ€๋Šฅ)
  2. ์ง„์งœ URL ์ƒ์„ฑํ•˜๊ธฐ(๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ด๋ฏธ์ง€์— ์ ‘๊ทผ ๊ฐ€๋Šฅ)

์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•: ๊ฐ€์งœ URL ์ƒ์„ฑ(๋‚ด ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์ด๋ฏธ์ง€์— ์ ‘๊ทผ ๊ฐ€๋Šฅ)

export default function ImageUploadPreviewPage() {
	const [imageUrl, setImageUrl] = useState("")

	const onChangeFile=(event: ChangeEvent<HTMLInputElement>)=> {
		const file = event.target.files?.[0]
		console.log(file);
			// ํŒŒ์ผ์ด ์—†์œผ๋ฉด ํ•จ์ˆ˜๋ฅผ ์ข…๋ฃŒ
			if (!file) {
				alert("ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค.");
				return
			}
		
	// 1. ์ž„์‹œ URL ์ƒ์„ฑ -> ๊ฐ€์งœ URL์ƒ์„ฑ, ๋‚ด ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
	const result = URL.createObjectURL(file)
	console.log(result)
	setImageUrl(result)
		
}
	return (
		<>
			<div>
				<input type="file" onChange={onChangeFile} />
				<img src={imageUrl}/>
			</div>
		</>
	);
}

๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•: ์ง„์งœ URL ์ƒ์„ฑ(๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ด๋ฏธ์ง€ ์ ‘๊ทผ ๊ฐ€๋Šฅ)

new FileReader() ๊ธฐ๋Šฅ์€ ํŒŒ์ผ ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ๋‚ด์šฉ์„ ์ฝ๊ณ  ์‚ฌ์šฉ์ž ์ปดํ“จํ„ฐ์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›ํ•ด์ฃผ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค. new FileReader() ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด new FileReader()์— ์žˆ๋Š” ๊ธฐ๋Šฅ๋“ค์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

// ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ œ์™ธํ•˜๊ณ  ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•๊ณผ ์ฝ”๋“œ ๋™์ผ

// 2. ์ž„์‹œ URL์ƒ์„ฑ -> ์ง„์งœ URL์ƒ์„ฑ, ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅ	
		const fileReader = new FileReader()
        // readAsDataURL(): Data URL์„ ์–ป์„ ์ˆ˜ ์žˆ์Œ.
		fileReader.readAsDataURL(file);
 // ํŒŒ์ผ ์ฝ๊ธฐ์— ์„ฑ๊ณตํ•˜๋ฉด onload ์‹คํ–‰. 
// onload์—์„œ๋Š” ํŒŒ์ผ์„ ์ฝ๊ณ  ์ƒ์„ฑ๋œ Data URL์ด target.result์— ๋‹ด๊ธฐ๊ฒŒ ๋จ.
		fileReader.onload = (data) => {
			// fileReader์˜ ๊ฒฐ๊ณผ๊ฐ’์ด string์ด ์•„๋‹ ์ˆ˜๋„ ์žˆ์œผ๋‹ˆ string์ผ๋•Œ๋งŒ ์‹คํ–‰๋˜๋„๋ก ํ•จ.
			if(typeof data.target?.result === "string"){
				console.log(data.target?.result);
				setImageUrl(data.target?.result)
			}	
		}
        
   // return ๊ฐ’ ์ฒซ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•๊ณผ ๋™์ผ

์ž„์‹œ URL๋กœ API ์š”์ฒญํ•˜๊ธฐ

์ž„์‹œ URL์„ ๋ฐ›์•„์˜ค๋Š” ๋ฐฉ๋ฒ• ์ค‘ ๋‘ ๋ฒˆ์งธ ๋ฐฉ๋ฒ•(์ง„์งœ URL ๋ฐ›์•„์˜ค๊ธฐ)์„ ์‚ฌ์šฉํ•ด์„œ API๋ฅผ ์š”์ฒญํ–ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ์ž„์‹œ URL์€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํŒŒ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— ์˜ฌ๋ฐ”๋ฅธ ํ˜•ํƒœ์˜ file ํƒ€์ž…์ด ์•„๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

// import ๋ถ€๋ถ„ ์ƒ๋žต

const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
    }
  }
`;

const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;

export default function ImageUploadPage() {
	// imageUrl์€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์œ„ํ•œ ์ฃผ์†Œ์ด๋ฏ€๋กœ ํ•ด๋‹น URL์„ ์Šคํ† ๋ฆฌ์ง€๋‚˜, DB์— ๋„ฃ์–ด์„œ๋Š” ์•ˆ๋จ.
  const [imageUrl, setImageUrl] = useState("");
	// DB์— ๋„ฃ๊ธฐ ์œ„ํ•œ URL์ฃผ์†Œ
  const [file, setFile] = useState<File>();

  const [ uploadFile ] = useMutation<Pick<IMutation, "uploadFile">,IMutationUploadFileArgs>(UPLOAD_FILE);
	const [ ๋‚˜์˜ํ•จ์ˆ˜ ] = useMutation(CREATE_BOARD);

	// ๋ฐ›์•„์˜จ ์ฃผ์†Œ๋กœ api ์š”์ฒญ
  const onClickSubmit = async () => {
		// ์Šคํ† ๋ฆฌ์ง€์— ์—…๋กœ๋“œ 
    const resultFile = await uploadFile({ variables: { file } });
    const url = resultFile.data?.uploadFile.url;
		
		// ๊ฒŒ์‹œ๊ธ€์— ์ด๋ฏธ์ง€ ๋“ฑ๋ก
    const result = await ๋‚˜์˜ํ•จ์ˆ˜({
      variables: {
        createBoardInput: {
          writer: "์ฒ ์ˆ˜",
          password: "1234",
          title: "์•ˆ๋…•ํ•˜์„ธ์š”",
          contents: "๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค",
          images: [url],
        },
      },
    });
    console.log(result);
  };

  const onChangeFile = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0]; 
    // <input type="file" multiple /> ์—์„œ multiple ์†์„ฑ์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ ๋“œ๋ž˜๊ทธ ๊ฐ€๋Šฅ
    if (!file) return;
    console.log(file);

    // 2. ์ž„์‹œURL ์ƒ์„ฑ => (์ง„์งœURL - ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
    const fileReader = new FileReader();
    fileReader.readAsDataURL(file);
    fileReader.onload = (event) => {
      if (typeof event.target?.result === "string") {
        console.log(event.target?.result); 
        // ๋ฏธ๋ฆฌ๋ณด๊ธฐ์šฉ
		setImageUrl(event.target?.result);
		// DB์— ๋„ฃ์–ด์ฃผ๊ธฐ์šฉ
        setFile(file);
  };

  return (
    <>
      <input type="file" onChange={onChangeFile} />
      <img src={imageUrl} />
      {/* <img src={`https://storage.googleapis.com/${imageUrl}`} /> */}

      <button onClick={onClickSubmit}>๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋กํ•˜๊ธฐ</button>
    </>
  );
}

createObjectURL?
createObjectURL๊ณผ fileReader ๋ชจ๋‘ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์™€์„œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค. createObjectURL์„ ์‚ฌ์šฉํ•˜๋ฉด ์†Œ์Šค์ฝ”๋“œ๊ฐ€ ์งง์•„์ ธ์„œ ์ž‘์„ฑํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ, fileReader์™€ ๋‹ฌ๋ฆฌ blob ๊ฐ์ฒด๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์™€ ํ†ต์‹ ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค! ๋˜ํ•œ, fileReader๊ฐ€ ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ๋„ ๋” ์ข‹๊ธฐ ๋•Œ๋ฌธ์— fileReader๋ฅผ ์‚ฌ์šฉํ•˜์ž.


Promise & Promise.all()


Promise

์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด result1๋ถ€ํ„ฐ 2, 3๊นŒ์ง€ ์ฐจ๋ก€๋กœ ์‹คํ–‰๋˜๊ณ  ์ด 6์ดˆ์˜ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค.

const startPromise = async () => {
  // console.time: ์‹œ๊ฐ„ ์ธก์ •
        console.time("=== ๊ฐœ๋ณ„ Promise ๊ฐ๊ฐ ===");
        const result1 = await new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve("์„ฑ๊ณต");
          }, 2000);
        });
        const result2 = await new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve("์„ฑ๊ณต");
          }, 3000);
        });
        const result3 = await new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve("์„ฑ๊ณต");
          }, 1000);
        });
        console.timeEnd("=== ๊ฐœ๋ณ„ Promise ๊ฐ๊ฐ ===");
      };

Promise.all()

๋ฐ˜๋ฉด, Promise.all()์„ ์‚ฌ์šฉํ•˜๋ฉด Promise.all()์— ํฌํ•จ๋œ ํ•จ์ˆ˜๋“ค์„ ๋™์‹œ์— ์‹คํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•ฝ 3์ดˆ์˜ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค.

	const startPromiseAll = async () => {
        // await Promise.all([promise, promise, promise])

        console.time("=== ํ•œ๋ฐฉ Promise.all ===");
        const result = await Promise.all([
          new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("์„ฑ๊ณต");
            }, 2000);
          }),
          new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("์„ฑ๊ณต");
            }, 3000);
          }),
          new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("์„ฑ๊ณต");
            }, 1000);
          }),
        ]);
        console.log(result);
        console.timeEnd("=== ํ•œ๋ฐฉ Promise.all ===");
      };

Promise.all()์˜ ๊ฒฐ๊ณผ๊ฐ’์€ Promise์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฐฐ์—ด์— '์„ฑ๊ณต' 3๊ฐœ๊ฐ€ ๋‚˜๋ž€ํžˆ ๋“ค์–ด์˜จ๋‹ค. Promise์™€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ํ•˜์ง€๋งŒ, ์‹œ๊ฐ„์€ ํ›จ์”ฌ ๋‹จ์ถ•์‹œ์ผœ์ค€๋‹ค!


Promise.all()์„ ์‚ฌ์šฉํ•œ ๋‹ค์ค‘ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ํ•œ๋ฒˆ์— ์˜ฌ๋ฆฌ๊ธฐ ์œ„ํ•ด promise.all()๊ณผ map์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ–ˆ๋‹ค.

// import ๋ถ€๋ถ„ ์ƒ๋žต

// CREATE_BOARD API gql ์ฝ”๋“œ ์ƒ๋žต
// UPLOAD_FILE API gql ์ฝ”๋“œ ์ƒ๋žต

export default function ImageUploadPage() {
  const [imageUrls, setImageUrls] = useState(["", "", ""]);
  // ์ด๋ฏธ์ง€ url 3๊ฐœ๊ฐ€ ๋“ค์–ด๊ฐˆ ๋ฐฐ์—ด ๋งŒ๋“ค๊ธฐ
  const [files, setFiles] = useState<File[]>([]);

  const [uploadFile] = useMutation<
    Pick<IMutation, "uploadFile">,
    IMutationUploadFileArgs
  >(UPLOAD_FILE);

  const [๋‚˜์˜ํ•จ์ˆ˜] = useMutation(CREATE_BOARD);

  const onClickSubmit = async () => {
    // Promise.all ์‚ฌ์šฉ!
     const results = await Promise.all([
       uploadFile({ variables: { file: files[0] } }),
       uploadFile({ variables: { file: files[1] } }),
       uploadFile({ variables: { file: files[2] } }),
     ]);
     console.log(results); // [resultFile0, resultFile1, resultFile2]
    
    // map ์‚ฌ์šฉ!
     const resultUrls = results.map((el) => (el ? el.data?.uploadFile.url : "")); // [dog1.jpg, dog2.jpg, dog3.jpg]

    const result = await ๋‚˜์˜ํ•จ์ˆ˜({
      variables: {
        createBoardInput: {
          writer: "์ฒ ์ˆ˜",
          password: "1234",
          title: "์•ˆ๋…•ํ•˜์„ธ์š”",
          contents: "๋ฐ˜๊ฐ‘์Šต๋‹ˆ๋‹ค",
          images: resultUrls, // [url0, url1, url2]
        },
      },
    });
    console.log(result);
  };

  const onChangeFile =
    (index: number) => async (event: ChangeEvent<HTMLInputElement>) => {
      const file = event.target.files?.[0]; // <input type="file" multiple /> ์—์„œ multiple ์†์„ฑ์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ ๋“œ๋ž˜๊ทธ ๊ฐ€๋Šฅ
      if (!file) return;
      console.log(file);

      // 2. ์ž„์‹œURL ์ƒ์„ฑ => (์ง„์งœURL - ๋‹ค๋ฅธ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
      const fileReader = new FileReader();
      fileReader.readAsDataURL(file);
      fileReader.onload = (event) => {
        if (typeof event.target?.result === "string") {
          console.log(event.target?.result); 

          const tempUrls = [...imageUrls];
          tempUrls[index] = event.target?.result;
          setImageUrls(tempUrls);

          const tempFiles = [...files];
          tempFiles[index] = file;
          setFiles(tempFiles);
    };

  return (
    <>
      <input type="file" onChange={onChangeFile(0)} />
      <input type="file" onChange={onChangeFile(1)} />
      <input type="file" onChange={onChangeFile(2)} />
      <img src={imageUrls[0]} />
      <img src={imageUrls[1]} />
      <img src={imageUrls[2]} />

      <button onClick={onClickSubmit}>๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋กํ•˜๊ธฐ</button>
    </>
  );
}

์—ฌ๊ธฐ์„œ ๋ฐ˜๋ณต๋˜๋Š” ๋ถ€๋ถ„์˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณต๋ฌธ์„ ํ†ตํ•ด ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋ฐ”๊ฟ”๋ณด์•˜๋‹ค.

const onClickSubmit = async () => {
    // 1. Promise.all ์ผ์„ ๋•Œ
    // const results = await Promise.all([
    //   uploadFile({ variables: { file: files[0] } }),
    //   uploadFile({ variables: { file: files[1] } }),
    //   uploadFile({ variables: { file: files[2] } }),
    // ]);
    // console.log(results); // [resultFile0, resultFile1, resultFile2]
    // const resultUrls = results.map((el) => (el ? el.data?.uploadFile.url : "")); // [dog1.jpg, dog2.jpg, dog3.jpg]

    // 2. Promise.all ์ผ์„ ๋•Œ - ๋ฆฌํŒฉํ† ๋ง 
    // files - [File0, File1, File2]
    // files.map(el => uploadFile({ variables: { file: el } })) // [uploadFile({ ...: File0 }), uploadFile({ ...: File1 }), uploadFile({ ...: File2 })]
    const results = await Promise.all(
      files.map((el) => el && uploadFile({ variables: { file: el } }))
    );
    console.log(results); // [resultFile0, resultFile1, resultFile2]
    const resultUrls = results.map((el) => (el ? el.data?.uploadFile.url : "")); // [dog1.jpg, dog2.jpg, dog3.jpg]

LazyLoad vs PreLoad


LazyLoad

ํŽ˜์ด์ง€ ๋ Œ๋”๋ง ์‹œ ์ค‘์š”ํ•˜์ง€ ์•Š์€ ๋ฆฌ์†Œ์Šค์˜ ๋กœ๋”ฉ์„ ๋‚˜์ค‘์œผ๋กœ ๋ฏธ๋ฃจ๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋ฏธ์ง€๊ฐ€ 10์žฅ์ด ๋„˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•˜๋ฉด, ํ•œ๋ฒˆ์— 10์žฅ ๋ชจ๋‘ ๋‹ค ๋กœ๋“œ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋ฉด, ํŽ˜์ด์ง€์˜ ๋กœ๋”ฉ์ด ๊ธธ์–ด์งˆ ๊ฒƒ์ด๋‹ค.

ํ•˜์ง€๋งŒ, ๋งจ ์œ„์˜ ํ™”๋ฉด์— ๋ณด์ด๋Š” ์ด๋ฏธ์ง€๋งŒ ๋กœ๋“œํ•œ ํ›„์— ์Šคํฌ๋กค์„ ๋‚ด๋ฆฌ๋ฉด์„œ ์ด๋ฏธ์ง€๊ฐ€ ๋ณด์—ฌ์ ธ์•ผ ํ•  ๋•Œ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•œ๋‹ค๋ฉด, ๋ฐ์ดํ„ฐ ๋‚ญ๋น„๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ๋‹ค!


PreLoad

PreLoad๋Š” ํŽ˜์ด์ง€๋ฅผ ์ฝ์„ ๋•Œ ๋ฏธ๋ฆฌ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ›์•„๋†“๋Š” ๊ธฐ์ˆ ์ด๋‹ค. LazyLoad์—์„œ์˜ ์˜ˆ์‹œ์™€ ๊ฐ™์ด ์ด๋ฏธ์ง€๊ฐ€ 10์žฅ์ด ๋„˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž. PreLoad์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋“ค์„ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•ด๋†“๊ณ  ๋Œ€๊ธฐํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.


๋‹ค์Œ์€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ธฐ ์ „์— ์ด๋ฏธ์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ›์•„์˜ค๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ ์†Œ์Šค์ฝ”๋“œ๋‹ค.

import { useRouter } from "next/router";
import { useEffect } from "react";

const qqq = [];

export default function ImagePreloadPage(): JSX.Element {
  const router = useRouter();

  useEffect(() => {
    // ์ด๋ฏธ์ง€ ํƒœ๊ทธ ์ƒ์„ฑ
    const img = new Image();
    // img.src = "์šฉ๋Ÿ‰ํฐ๋ฐฐ๋„ˆ์ด๋ฏธ์ง€.jpg";
    img.src = "/IMG_9822.PNG";
    // img ํƒœ๊ทธ๊ฐ€ onload ๋˜์—ˆ์„ ๋•Œ 
    // ์ด๋ฏธ์ง€๋“ค์„ qqq ๋ฐฐ์—ด์— ๋„ฃ์–ด์คŒ
    img.onload = () => {
      qqq.push(img);
    };
  });

  const onClickMove = (): void => {
    void router.push("/section31/31-09-image-preload-moved");
  };

  return <button onClick={onClickMove}>ํŽ˜์ด์ง€ ์ด๋™ํ•˜๊ธฐ</button>;
}

Prefetch

๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ , ๋ชฉ๋ก ์ค‘ ํ•˜๋‚˜์— ๋งˆ์šฐ์Šค๋ฅผ ๊ฐ€์ ธ๋‹ค ๋†จ์„ ๋•Œ ๋ฏธ๋ฆฌ ๊ฒŒ์‹œ๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

// import ๋ถ€๋ถ„ ์ƒ๋žต

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 StaticRoutedPage() {
  const router = useRouter();
  const client = useApolloClient();
  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
  >(FETCH_BOARDS);

  console.log(data?.fetchBoards);

 // cache์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ ์ €์žฅ๋˜๊ณ , ํŽ˜์ด์ง€ ์ด๋™ํ•˜๋ฉด ๋ฐฑ์—”๋“œ์— ๊ฐ€์ง€ ์•Š๊ณ , cache์— ์žˆ๋Š” ๊ฒƒ์„ ๊ฐ€์ง€๊ณ  ์˜ด.
  const prefetchBoard = (boardId: string) => async () => {
    await client.query({
      query: FETCH_BOARD,
      variables: { boardId },
    });
  };

  const onClickMove = (boardId: string) => () => {
    void router.push(`/32-08-data-prefetch-moved/${boardId}`);
  };

  return (
    <>
      {data?.fetchBoards.map((el) => (
        <div key={el._id}>
          <span style={{ margin: "10px" }}>{el.writer}</span>
				{/* ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„ ๋•Œ data๋ฅผ ๋ฐ›์•„์˜ค๋„๋ก */}
          <span
            style={{ margin: "10px" }}
            onMouseOver={prefetchBoard(el._id)}
            onClick={onClickMove(el._id)}
          >
            {el.title}
          </span>
        </div>
      ))}
    </>
  );
}

๋ฏธ๋ฆฌ ๊ฒŒ์‹œ๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ๋†“์œผ๋ฉด, ๊ฒŒ์‹œ๊ธ€์„ ํด๋ฆญํ•ด์„œ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ๋“ค์–ด๊ฐ”์„ ๋•Œ ๋” ๋น ๋ฅด๊ฒŒ ๊ฒŒ์‹œ๊ธ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค!


์ด๋ฏธ์ง€ ์„ฑ๋Šฅ ๊ด€๋ จ ์œ ์šฉํ•œ ์‚ฌ์ดํŠธ


Google PageSpeed Insights

์‹ค์ œ ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ๋‚˜์„œ, ๋‚ด๊ฐ€ ๋ฐฐํฌํ•œ ํŽ˜์ด์ง€์˜ ๊ฐœ์„ ํ•  ์ ์„ ์ฐพ์„๋•Œ ์œ ์šฉํ•œ ์‚ฌ์ดํŠธ๋‹ค.


์ด๋ฏธ์ง€ Webp ํ™•์žฅ์ž

png, jpeg์™€ ๊ฐ™์€ ์ด๋ฏธ์ง€ ํ™•์žฅ์ž๋‹ค. ์ด๋ฏธ์ง€ ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์„ ์ค„์ด๊ณ , ์„œ๋ฒ„๋น„๋ฅผ ์•„๋ผ๊ธฐ ์œ„ํ•ด ๊ตฌ๊ธ€์—์„œ ๋งŒ๋“ค์—ˆ๋‹ค.

Webp์˜ ์žฅ์ 

Webp๋Š” GIF, PNG, JPEG ํ™•์žฅ์ž ๋ชจ๋‘ ๋Œ€์ฒด ๊ฐ€๋Šฅํ•œ ํ™•์žฅ์ž์ด๊ณ , ์ด๋ฏธ์ง€๋ฅผ ํŒŒ์ผ์„ ์••์ถ•ํ–ˆ์„ ๋•Œ ๊ธฐ์กด PNG, JPEG๋ณด๋‹ค ์•ฝ 30%์ •๋„ ์šฉ๋Ÿ‰์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค!


Webp ํ™•์žฅ์ž ๋ณ€ํ™˜ํ•˜๊ธฐ

Webp ํ™•์žฅ์ž ๋ณ€ํ™˜ ์‚ฌ์ดํŠธ

์‚ฌ์šฉ๋ฐฉ๋ฒ•!
Select File ํด๋ฆญ > ์–ด๋–ค ํŒŒ์ผ๋กœ ๋ณ€ํ™˜ํ• ์ง€ ์„ ํƒ(Webp ์„ ํƒ) > convert ํด๋ฆญ



์ด๋ฏธ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  1. React-lazy-load: ์Šคํฌ๋กค์ด ๋‚ด๋ ค๊ฐ€๋ฉด์„œ ํ•ด๋‹น ์ด๋ฏธ์ง€๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ฑ์žฅํ•  ๋•Œ ์ด๋ฏธ์ง€ ๋‹ค์šด๋กœ๋“œ
  2. React-dropzone: ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋ž
  3. React-avator-editor
  4. ant-design
  5. React-beautiful-dnd

์ฐธ๊ณ ! ๊ทธ ์™ธ์˜ ์œ ์šฉํ•œ ์‚ฌ์ดํŠธ!
wappalyzer(ํฌ๋กฌ ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ): ํŠน์ • ํŽ˜์ด์ง€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ์ˆ  ์•Œ ์ˆ˜ ์žˆ๋‹ค.
์ฝ”๋“œ๋„ˆ๋ฆฌ: ๊ธฐ์—…๋ณ„ ์“ฐ๋Š” ๊ธฐ์ˆ 



profile
Web FE ๊ฐœ๋ฐœ์ž ์ทจ์ค€์ƒ

0๊ฐœ์˜ ๋Œ“๊ธ€