Front-end/Optimizing

[Optimize] TTV (Time to Viewport): ์›น์‚ฌ์ดํŠธ์˜ ์ฒซ ๋ทฐ ๋ Œ๋”๋ง ๐ŸŒ„

xeunnie 2025. 1. 22. 01:00
728x90
๋ฐ˜์‘ํ˜•

TTV (Time to Viewport) ํ†บ์•„๋ณด๊ธฐ! ๐ŸŒ„

TTV๋Š” Time to Viewport์˜ ์•ฝ์ž๋กœ, ์‚ฌ์šฉ์ž๊ฐ€ ์›น ํŽ˜์ด์ง€๋ฅผ ์—ด์—ˆ์„ ๋•Œ, ์ฒซ ํ™”๋ฉด(๋ทฐํฌํŠธ)์ด ๋ Œ๋”๋ง๋˜๊ธฐ๊นŒ์ง€ ๊ฑธ๋ฆฌ๋Š” ์‹œ๊ฐ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์›น ์„ฑ๋Šฅ ์ง€ํ‘œ ์ค‘ ํ•˜๋‚˜๋กœ, ์ฒซ ์ธ์ƒ์„ ์ขŒ์šฐํ•˜๋Š” ๋ฐ ์ค‘์š”ํ•œ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์—์„œ๋Š” TTV๊ฐ€ ๋ฌด์—‡์ธ์ง€, ์™œ ์ค‘์š”ํ•œ์ง€, ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๊นŒ์ง€ ๊นŠ์ด ํŒŒํ—ค์ณ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


๐ŸŽฏ TTV๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”?

TTV๋Š” ์›น์‚ฌ์ดํŠธ๊ฐ€ ์‚ฌ์šฉ์ž ํ™”๋ฉด์— ์ฒซ ๋ฒˆ์งธ ์œ ์˜๋ฏธํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ๊นŒ์ง€์˜ ์‹œ๊ฐ„์„ ์ธก์ •ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์—ด์—ˆ์„ ๋•Œ ์•„๋ฌด๊ฒƒ๋„ ๋ณด์ด์ง€ ์•Š๋Š” ์ƒํƒœ์—์„œ ์ฒซ ์ฝ˜ํ…์ธ ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋Š” ์ˆœ๊ฐ„๊นŒ์ง€์˜ ์‹œ๊ฐ„์ธ First Paint์™€ ์œ ์‚ฌํ•˜์ง€๋งŒ, TTV๋Š” ์œ ์ €๊ฐ€ ์‹ค์ œ๋กœ ๋ณด๋Š” ํ™”๋ฉด์— ์ดˆ์ ์„ ๋งž์ถ˜๋‹ค๋Š” ์ ์—์„œ ์ฐจ์ด๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด:

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์›น์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•ฉ๋‹ˆ๋‹ค.
  2. ๋กœ๊ณ ๋‚˜ ํ—ค๋” ์ด๋ฏธ์ง€๊ฐ€ ํ™”๋ฉด์— ๋ Œ๋”๋ง๋ฉ๋‹ˆ๋‹ค.
  3. ์‚ฌ์šฉ์ž๋Š” “์•„! ์ด์ œ ๋ฌด์–ธ๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์‹œ์ž‘ํ–ˆ๊ตฌ๋‚˜!“๋ผ๊ณ  ๋А๋ผ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋ฐ”๋กœ ์ด ์ˆœ๊ฐ„์ด   TTV ์ž…๋‹ˆ๋‹ค.


๐Ÿค” TTV๊ฐ€ ์ค‘์š”ํ•œ ์ด์œ ๋Š”?

1๏ธโƒฃ ์‚ฌ์šฉ์ž ์ฒซ์ธ์ƒ์— ์ง๊ฒฐ

์‚ฌ์šฉ์ž๋Š” ํ™”๋ฉด์ด ์•„๋ฌด๊ฒƒ๋„ ๋กœ๋“œ๋˜์ง€ ์•Š๋Š” ์ƒํƒœ์—์„œ ์˜ค๋ž˜ ๊ธฐ๋‹ค๋ฆฌ๋ฉด “์ด ์‚ฌ์ดํŠธ๋Š” ๋А๋ฆฌ๋‹ค”๊ณ  ๋А๋ผ๊ณ  ๋– ๋‚  ํ™•๋ฅ ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.

2๏ธโƒฃ ์›น ์„ฑ๋Šฅ๊ณผ ์ง๊ฒฐ

Google์˜ ์—ฐ๊ตฌ์— ๋”ฐ๋ฅด๋ฉด, ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ๊ฐ„์ด 1์ดˆ์—์„œ 3์ดˆ๋กœ ๋Š˜์–ด๋‚˜๋ฉด ์ดํƒˆ๋ฅ ์ด 32% ์ฆ๊ฐ€ํ•œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

TTV๊ฐ€ ์งง์„์ˆ˜๋ก ์‚ฌ์šฉ์ž ๋งŒ์กฑ๋„์™€ ์ „ํ™˜์œจ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.

3๏ธโƒฃ SEO ์ตœ์ ํ™”

TTV๋Š” ํŽ˜์ด์ง€ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฉฐ, Google๊ณผ ๊ฐ™์€ ๊ฒ€์ƒ‰ ์—”์ง„์˜ ๋žญํ‚น ์•Œ๊ณ ๋ฆฌ์ฆ˜์—๋„ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.


๐Ÿ›  TTV ์ตœ์ ํ™” ๋ฐฉ๋ฒ•

1๏ธโƒฃ  Critical Rendering Path ์ตœ์ ํ™”

TTV๋ฅผ ๊ฐœ์„ ํ•˜๋ ค๋ฉด, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ค‘์š”ํ•œ ์ฝ˜ํ…์ธ ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ํฌ๋ฆฌํ‹ฐ์ปฌ ๋ Œ๋”๋ง ๊ฒฝ๋กœ๋ฅผ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

1. Render Blocking ๋ฆฌ์†Œ์Šค ์ œ๊ฑฐ

CSS, JavaScript ๋“ฑ ์ค‘์š”ํ•œ ๋ฆฌ์†Œ์Šค๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „๊นŒ์ง€ ํ™”๋ฉด์ด ๋นˆ ์ƒํƒœ๋กœ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด ๋ฆฌ์†Œ์Šค๋ฅผ ๋น„๋™๊ธฐ๋กœ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜, ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋งŒ ์šฐ์„ ์ ์œผ๋กœ ๋กœ๋“œํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

<link rel="stylesheet" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

2. ํฐํŠธ ๋””์Šคํ”Œ๋ ˆ์ด ์ตœ์ ํ™”

์›นํฐํŠธ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ํ…์ŠคํŠธ๊ฐ€ ๋ Œ๋”๋ง๋˜์ง€ ์•Š๊ณ  ๋นˆ์นธ์œผ๋กœ ๋‚จ์•„ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. font-display: swap์„ ์‚ฌ์šฉํ•˜๋ฉด ํฐํŠธ ๋กœ๋“œ ์ „์— ๊ธฐ๋ณธ ํฐํŠธ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
  font-display: swap;
}

2๏ธโƒฃ Lazy Loading ์ ์šฉ

Lazy Loading์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž ํ™”๋ฉด์— ํ•„์š”ํ•œ ์ด๋ฏธ์ง€๋‚˜ ์ฝ˜ํ…์ธ ๋งŒ ์šฐ์„ ์ ์œผ๋กœ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

<img src="placeholder.jpg" data-src="real-image.jpg" loading="lazy" />

3๏ธโƒฃ  Server-Side Rendering (SSR) ํ™œ์šฉ

React, Vue ๋“ฑ SPA ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ SSR์„ ์‚ฌ์šฉํ•˜๋ฉด ์ดˆ๊ธฐ HTML์„ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋น ๋ฅด๊ฒŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

// React ์˜ˆ์ œ
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import App from './App';

const serverRender = () => {
  const content = ReactDOMServer.renderToString(<App />);
  return `
    <html>
      <body>
        <div id="root">${content}</div>
        <script src="bundle.js"></script>
      </body>
    </html>
  `;
};

4๏ธโƒฃ  CDN(Content Delivery Network) ํ™œ์šฉ

CDN์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž์˜ ์ง€๋ฆฌ์  ์œ„์น˜์™€ ๊ฐ€๊นŒ์šด ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•˜์—ฌ ๋ฆฌ์†Œ์Šค ๋กœ๋“œ ์‹œ๊ฐ„์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

5๏ธโƒฃ ์ด๋ฏธ์ง€ ์ตœ์ ํ™”

์ด๋ฏธ์ง€๋Š” TTV์— ๊ฐ€์žฅ ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.

  • WebP์™€ ๊ฐ™์€ ์ตœ์‹  ์ด๋ฏธ์ง€ ํฌ๋งท ์‚ฌ์šฉ
  • ํฌ๊ธฐ๊ฐ€ ํฐ ์ด๋ฏธ์ง€์˜ ํ•ด์ƒ๋„๋ฅผ ์ค„์ด๊ณ , ์••์ถ•๋ฅ ์„ ๋†’์ด๊ธฐ

๐ŸŒŸ TTV ๊ด€๋ จ ์ฃผ์š” ๋„๊ตฌ

TTV๋ฅผ ์ธก์ •ํ•˜๊ฑฐ๋‚˜ ์ตœ์ ํ™” ์ƒํƒœ๋ฅผ ์ ๊ฒ€ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๋„๊ตฌ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

  1. Google Lighthouse: TTV๋ฅผ ๋น„๋กฏํ•œ ๋‹ค์–‘ํ•œ ์„ฑ๋Šฅ ์ง€ํ‘œ๋ฅผ ๋ถ„์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. PageSpeed Insights: Google์—์„œ ์ œ๊ณตํ•˜๋Š” ์›น ์„ฑ๋Šฅ ์ ๊ฒ€ ๋„๊ตฌ๋กœ, TTV ๊ด€๋ จ ๊ฐœ์„ ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  3. WebPageTest: ์‹ค์ œ ๋„คํŠธ์›Œํฌ ํ™˜๊ฒฝ์—์„œ ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ๊ฐ„์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
  4. Chrome DevTools: ๋„คํŠธ์›Œํฌ ํƒญ์—์„œ ๋ฆฌ์†Œ์Šค ๋กœ๋“œ ์‹œ๊ฐ„์„ ๋ถ„์„

๐Ÿงช TTV ์‹ค์ „ ์ตœ์ ํ™”: ์˜ˆ์ œ

React์™€ Lazy Loading์„ ํ™œ์šฉํ•œ TTV ๊ฐœ์„  ์˜ˆ์ œ

import React, { Suspense, lazy } from "react";

const HeavyComponent = lazy(() => import("./HeavyComponent"));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

export default App;

Express.js์™€ SSR๋กœ TTV ์ตœ์ ํ™”

import express from "express";
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "./App";

const server = express();

server.get("/", (req, res) => {
  const content = ReactDOMServer.renderToString(<App />);
  res.send(`
    <html>
      <body>
        <div id="root">${content}</div>
        <script src="bundle.js"></script>
      </body>
    </html>
  `);
});

server.listen(3000, () => {
  console.log("Server is running on http://localhost:3000");
});

๐Ÿ’ก ๊ฒฐ๋ก 

TTV๋Š” ๋‹จ์ˆœํžˆ “ํ™”๋ฉด์— ๋ญ”๊ฐ€ ๋ณด์ธ๋‹ค”์—์„œ ๊ทธ์น˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜๊ณผ SEO, ์›น์‚ฌ์ดํŠธ ์„ฑ๋Šฅ ๋ชจ๋‘์™€ ์—ฐ๊ฒฐ๋œ ์ค‘์š”ํ•œ ์ง€ํ‘œ์ฃ . ์œ„์˜ ์ตœ์ ํ™” ๋ฐฉ๋ฒ•๋“ค์„ ์‹ค์ฒœํ•˜๋ฉด TTV๋ฅผ ์ค„์ด๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ์ตœ๊ณ ์˜ ์ฒซ์ธ์ƒ์„ ์„ ์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๐ŸŒท ์ „์„ค์˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด๋ด…์‹œ๋‹น! ๐ŸŒท

728x90
๋ฐ˜์‘ํ˜•