Soft Navigations - The-Next-Web-Research-Lab/the-next-web-research-lab.github.io GitHub Wiki

Overview

"Soft Navigation"์€ History API ๋˜๋Š” ์ƒˆ๋กœ์šด Navigation API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” JS ๊ธฐ๋ฐ˜์˜ same-document navigation์œผ๋กœ, ์‚ฌ์šฉ์ž ์ œ์Šค์ฒ˜์— ์˜ํ•ด ํŠธ๋ฆฌ๊ฑฐ๋˜์–ด ์ด์ „ ์ปจํ…์ธ ์™€ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋œ URL์„ ์ˆ˜์ •ํ•˜๊ณ  DOM์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.

PerformanceTimeline#168๋Š” Soft Navigation์— ๋Œ€ํ•œ ์„ฑ๋Šฅ ์ง€ํ‘œ๋ฅผ ๋” ์ž˜ ๋ฆฌํฌํŠธํ•  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ผ๋Š” ํฌ๋ง์„ ๊ฐ„์ถ”๋ ค์„œ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. Soft Navigation์„ ํƒ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ํœด๋ฆฌ์Šคํ‹ฑ์€ ๊ฐœ๋ฐœ์ž๊ฐ€ SPA์˜ ์„ฑ๋Šฅ ์ง€ํ‘œ๋ฅผ ์ธก์ •ํ•˜๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ด์ต์ด ๋˜๋„๋ก ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Motivation

์™œ ์šฐ๋ฆฌ๋Š” Soft Navigation์„ ์›น์ƒ์— ๋…ธ์ถœ์‹œํ‚ค๋ ค๊ณ  ํ• ๊นŒ์š”?

๋ช‡ ๊ฐ€์ง€ ์ด์œ :

  • ๊ฐœ๋ฐœ์ž๋“ค์€ ๋‹ค์–‘ํ•œ ์„ฑ๋Šฅ ํ•ญ๋ชฉ์„ ํŠน์ • "Soft Navigation" URL์— ๊ท€์†์‹œํ‚ค๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•˜๋‚˜์˜ URL์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ ์‹œํ”„ํŠธ๋Š” ํ˜„์žฌ ํ•ด๋‹น ๋žœ๋”ฉ ํŽ˜์ด์ง€์— ๊ท€์†๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ด๋กœ ์ธํ•ด ์ž˜๋ชป๋œ ์†์„ฑ์ด ๋ฐœ์ƒํ•˜๊ณ  ์‹ค์ œ ์›์ธ์„ ์ฐพ๊ณ  ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ์–ด๋ ค์›€์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์ž๋“ค์€ Soft Navigation์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ "Load" ์„ฑ๋Šฅ ํ•ญ๋ชฉ์„ ๋ฐ›๊ธฐ๋ฅผ ์›ํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ, ํŽ˜์ธํŠธ ํƒ€์ด๋ฐ ํ•ญ๋ชฉ์€ ๊ทธ๋Ÿฌํ•œ ๋‚ด๋น„๊ฒŒ์ด์…˜์— ์›ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

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

Proposed Heuristics

  • ์‚ฌ์šฉ์ž๋Š” DOM Element๋ฅผ ํด๋ฆญํ•˜์—ฌ soft navigation์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
    • ์šฐ๋ฆฌ๋Š” semantic elements๋งŒ ๊ณ ๋ คํ–ˆ์ง€๋งŒ, ์ด๊ฒƒ์€ ํ˜„์žฌ ํ˜„์‹ค ์„ธ๊ณ„์˜ ๊ด€๋ก€์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.
  • ์ด ์ž‘์—…์œผ๋กœ ์ธํ•ด ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค("ํด๋ฆญ" ์ด๋ฒคํŠธ ๋˜๋Š” "๋‚ด๋น„๊ฒŒ์ด์…˜" ์ด๋ฒคํŠธ)
  • ๊ทธ๋Ÿฐ ๋‹ค์Œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์— ์˜ํ•ด ํŠธ๋ฆฌ๊ฑฐ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • "๋‚ด๋น„๊ฒŒ์ด์…˜" ์ด๋ฒคํŠธ์ธ ๊ฒฝ์šฐ ์ด๋Ÿฌํ•œ ์ž‘์—…์€ traverseTo()์— ์ „๋‹ฌ๋œ Promise์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.
    • "ํด๋ฆญ" ์ด๋ฒคํŠธ์ธ ๊ฒฝ์šฐ ํ•ด๋‹น ์ž‘์—…์€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ์ž์ฒด์—์„œ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
  • "ํด๋ฆญ" ์ด๋ฒคํŠธ์˜ ๊ฒฝ์šฐ ํ•ธ๋“ค๋Ÿฌ๋Š” History.pushState() ๋˜๋Š” History.placeState() ํ˜ธ์ถœ ๋˜๋Š” ๋ฌธ์„œ ์œ„์น˜ ๋ณ€๊ฒฝ์„ ํฌํ•จํ•˜๋Š” ํƒœ์Šคํฌ๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ์ž‘์—…์€ DOM ์š”์†Œ๋ฅผ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.
    • ์šฐ๋ฆฌ๋Š” ํœด๋ฆฌ์Šคํ‹ฑ์ด ๋„ˆ๋ฌด ๊ด‘๋ฒ”์œ„ํ•˜๊ณ  ๋‚ด๋น„๊ฒŒ์ด์…˜์œผ๋กœ ๊ฐ„์ฃผ๋˜์–ด์„œ๋Š” ์•ˆ ๋˜๋Š” ์ˆ˜์ • ์‚ฌํ•ญ์„ ์บก์ฒ˜ํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•˜์—ฌ "์˜๋ฏธ ์žˆ๋Š”" DOM ์ˆ˜์ • ์‚ฌํ•ญ๊ณผ ๊ด€๋ จํ•˜์—ฌ ํŠน์ • DOM ์š”์†Œ ๋˜๋Š” ๊ธฐํƒ€ ํœด๋ฆฌ์Šคํ‹ฑ์œผ๋กœ ์ œํ•œํ•˜๋ ค๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • contentful element๊ฐ€ ํฌํ•จ๋œ ๋‹ค์Œ ํŽ˜์ธํŠธ๋Š” Soft Navigation์˜ FCP๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.
  • ๋‹ค์Œ largest contentful element๋Š” LCP ํ•ญ๋ชฉ์„ ํŠธ๋ฆฌ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
  • ๋งˆ์ง€๋ง‰์œผ๋กœ, ์šฐ๋ฆฌ๋Š” ํŠน์ • ๊ธฐ๊ฐ„(์˜ˆ: Y์ดˆ๋‹น X)์— ํƒ์ง€๋˜๋Š” Soft Navigation์˜ ์–‘์„ ์ œํ•œํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Task attribution

์œ„์˜ ํœด๋ฆฌ์Šคํ‹ฑ์€ ์ž‘์—…์„ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ๊ณผ ๊ทธ ์ฆ๋ช…๋ ฅ์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ํŠน์ • ์ž‘์—…์ด ๋‹ค๋ฅธ ์‚ฌ๋žŒ์— ์˜ํ•ด ๊ฒŒ์‹œ๋˜์—ˆ๋‹ค๋Š” ๊ฒƒ์„ ๋งํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๋ฉฐ, DOM dirtying์™€ Soft Navigation์„ ํŠธ๋ฆฌ๊ฑฐํ•œ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ์˜ URL ์ˆ˜์ • ์‚ฌ์ด์— ์ธ๊ณผ๊ด€๊ณ„ ์ฒด์ธ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Note: ์œ„์˜ ํœด๋ฆฌ์Šคํ‹ฑ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ง€์ •ํ•˜๋ ค๋ฉด ์ด๋ฒคํŠธ ๋ฃจํ”„ ์ฒ˜๋ฆฌ์˜ ์ผ๋ถ€๋กœ TaskAttribution์„ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Proposed API shape

SoftNavigationEntry : PerformanceEntry {
  unsigned long NavigationId;
}

NavigationId๋Š” Performance Timeline ๋‚ด์— ์ •์˜๋ฉ๋‹ˆ๋‹ค. (explainer)

PerformanceEntry ์ƒ์†์˜ ์˜๋ฏธ๋Š” startTime, name, entryType, duration`์„ ํฌํ•จํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  • startTime์€ ์‚ฌ์šฉ์ž์˜ ํด๋ฆญ์„ ๋ฐ›์€ ์‹œ์ ์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • name์€ Soft Navigation์„ ๋‚˜ํƒ€๋‚ด๋Š” ํžˆ์Šคํ† ๋ฆฌ ํ•ญ๋ชฉ์˜ URL ์ž…๋‹ˆ๋‹ค.
  • entryType์€ "soft-navigation"์ž…๋‹ˆ๋‹ค.
  • duration์€ ์•„์ง ์œ ์šฉํ•œ ๊ฐ’์ด ๋ช…ํ™•ํ•˜์ง€ ์•Š๋‹ค.

Examples

์•„์ง ๊ณต์‹ ๋ฐฐํฌ ์ „์œผ๋กœ chrome://flags/#enable-experimental-web-platform-features ํ™œ์„ฑํ™” ํ›„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

์ง€์› ํ™•์ธ

const isSupportedSoftNavigation = PerformanceObserver.supportedEntryTypes.includes('soft-navigation');

Soft Navigation ํ™•์ธ

const softNavigations = [];
const observer = new PerformanceObserver(list => {
  softNavigations.push(...list.getEntries());

  console.log(...list.getEntries());
});
observer.observe({ type: 'soft-navigation' });
{
    "name": "https://github.com/WICG/soft-navigations/blob/main/README.md",
    "entryType": "soft-navigation",
    "startTime": 343297.30000019073,
    "duration": 0,
    "navigationId": "0e8e49eb-9642-40e3-b212-36b8ae7b30c1"
}

LCP

const softNavigations = [];
const softNavigationObserver = new PerformanceObserver(list => {
    softNavigations.push(...list.getEntries());
    console.log(...list.getEntries());
});
softNavigationObserver.observe({
    type: 'soft-navigation'
});

const lcpObserver = new PerformanceObserver(list => {
    for (const entry of list.getEntries()) {
        const id = entry.navigationId;
        const nav = softNavigations.filter(entry => entry.navigationId === id)[0];
        console.log(entry);
    }
});
lcpObserver.observe({
    type: 'largest-contentful-paint',
    includeSoftNavigationObservations: true
});
// SoftNavigationEntry
{
    "name": "https://github.com/WICG/soft-navigations/blob/main/README.md",
    "entryType": "soft-navigation",
    "startTime": 20203.599999904633,
    "duration": 0,
    "navigationId": "4d5a5f9a-ff90-4371-bb28-c03dc4ceb48e"
}

// LargestContentfulPaint
{
    "name": "",
    "entryType": "largest-contentful-paint",
    "startTime": 20803.799999952316,
    "duration": 0,
    "navigationId": "4d5a5f9a-ff90-4371-bb28-c03dc4ceb48e",
    "size": 94129,
    "renderTime": 20803.799,
    "loadTime": 0,
    "firstAnimatedFrameTime": 0,
    "id": "",
    "url": ""
}