Progressive JSON

์›๋ฏผ๊ด€ยท2025๋…„ 6์›” 14์ผ

[TIL]

๋ชฉ๋ก ๋ณด๊ธฐ
185/201
post-thumbnail

Progressive JSON ๐ŸŽจ

0. Overview โœ๏ธ

์ด๋ฏธ์ง€๋ฅผ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ๋กœ๋”ฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ฒ˜์Œ์—๋Š” ํ๋ฆฟํ•˜๊ฒŒ ๋ณด์ด๋‹ค๊ฐ€ ์ ์  ์„ ๋ช…ํ•ด์ง€๋Š” ๋ฐฉ์‹์œผ๋กœ ๋กœ๋”ฉํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์ ์ง„์  JPEG๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ์•„์ด๋””์–ด๋ฅผ JSON ์ „์†ก ๋ฐฉ์‹์— ์ ์šฉํ•œ ๊ฒƒ์ด Progressive JSON์ž…๋‹ˆ๋‹ค.

const data = {
  header: 'Welcome to my blog',
  post: {
    content: 'This is my article',
    comments: [
      'First comment',
      'Second comment',
      // ...
    ]
  },
  footer: 'Hope you like it'
}

๊ธฐ์กด JSON์€ ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ๊นŒ์ง€ ๋ชจ๋‘ ๋กœ๋“œ๋˜์–ด์•ผ ์œ ํšจํ•œ ๊ฐ์ฒด ํŠธ๋ฆฌ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์•ผ๋งŒ JSON.parse๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด์ฃ . ๋งŒ์•ฝ JSON์˜ ์ผ๋ถ€ ์ƒ์„ฑ์— ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฐ๋‹ค๋ฉด, ํด๋ผ์ด์–ธํŠธ๋Š” ์„œ๋ฒ„๊ฐ€ ๋ชจ๋“  ์ž‘์—…์„ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ์–ด๋–ค ์ž‘์—…๋„ ์‹œ์ž‘ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.

์ด๋Š” ๋งˆ์น˜ ๋ชจ๋“  ๋‚ด์šฉ์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ดํ•ดํ•ด์•ผ๋งŒ ๋‹ค์Œ ๋‹จ์›์œผ๋กœ ๋„˜์–ด๊ฐ€๋Š” ํ•™์Šต ๋ฐฉ์‹๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ง‘ํ•ฉ๊ณผ ๋ช…์ œ๋ฅผ ๋ฐ•์‚ฌ ์ˆ˜์ค€์œผ๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ์ดํ•ดํ•˜์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด ํ•จ์ˆ˜์™€ ๋ฏธ์ ๋ถ„ ๋“ฑ ๋‹ค์Œ ๋‹จ์›์œผ๋กœ ๋„˜์–ด๊ฐ€์ง€ ๋ชปํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ง์ด์ฃ . ์ด๊ฒƒ์€ ์ข‹์€ ํ•™์Šต ๋ฐฉ์‹์ด๋ผ๊ณ  ๋ณด๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

1. Streaming JSON์˜ ํ•œ๊ณ„ โœ๏ธ

JSON ํŒŒ์„œ๋Š” JSON ๋ฌธ์ž์—ด์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์•ž์„œ ์ œ์‹œํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Streaming JSON Parser๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ๋ถˆ์™„์ „ํ•œ ์ž…๋ ฅ์œผ๋กœ๋ถ€ํ„ฐ ๊ฐ์ฒด ํŠธ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•˜๋Š” Parser์ž…๋‹ˆ๋‹ค.

{
  header: 'Welcome to my blog',
  post: {
    content: 'This is my article',
    comments: [
      'First comment',
      'Second comment'
      // ์—ฌ๊ธฐ์„œ ์ŠคํŠธ๋ฆผ์ด ๋Š์–ด์ง„ ์ƒํƒœ

์ด ์‹œ์ ์—์„œ ๊ฒฐ๊ณผ๋ฅผ ์š”์ฒญํ•˜๋ฉด, Streaming Parser๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค:

{
  header: 'Welcome to my blog',
  post: {
    content: 'This is my article',
    comments: [
      'First comment',
      'Second comment'
      // (๋‚˜๋จธ์ง€ ๋Œ“๊ธ€๋“ค์ด ๋ˆ„๋ฝ๋จ)
    ]
  }
  // (footer ์†์„ฑ์ด ๋ˆ„๋ฝ๋จ)
}

Streaming Parser์˜ ๋ฌธ์ œ์ :

  • ๊ฐ์ฒด๋“ค์ด ๋ถˆ์™„์ „ํ•˜๋‹ค๋Š” ์น˜๋ช…์ ์ธ ๋‹จ์ 
  • ์ตœ์ƒ์œ„ ๊ฐ์ฒด๋Š” header / post / footer ์„ธ ๊ฐœ์˜ ์†์„ฑ์„ ๊ฐ€์ ธ์•ผ ํ•˜์ง€๋งŒ footer๊ฐ€ ๋ˆ„๋ฝ
  • post๋Š” ๋” ๋งŽ์€ comments๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ ํ˜„์žฌ ์ƒํƒœ๋กœ๋Š” ํ™•์‹คํ•˜์ง€ ์•Š์Œ

์ด๋Š” ์—ฌ์ „ํžˆ ํ•˜ํ–ฅ์‹(top-down) ๋กœ๋”ฉ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€์˜ ์ƒ๋‹จ 10%๋Š” ์„ ๋ช…ํ•˜์ง€๋งŒ ์ „์ฒด ๊ทธ๋ฆผ์„ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ค์šด ๊ฒƒ์ฒ˜๋Ÿผ, top-down ๋ฐฉ์‹์˜ ๋ถˆ์™„์ „ํ•œ ๊ฐ์ฒด๋Š” ๊ทธ ์ž์ฒด๋กœ ๋ถˆ์•ˆ์ •์„ฑ์„ ๋‚ดํฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

2. Progressive JSON์˜ ํ•ต์‹ฌ ์•„์ด๋””์–ด โœ๏ธ

์ง€๊ธˆ๊นŒ์ง€ ์„ค๋ช…ํ•œ ๋ฐฉ์‹์€ ๊นŠ์ด ์šฐ์„ (depth-first) ๋ฐฉ์‹์˜ ๋ฐ์ดํ„ฐ ์ „์†ก์ž…๋‹ˆ๋‹ค. ์ตœ์ƒ์œ„ ๊ฐ์ฒด์˜ ์†์„ฑ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜์—ฌ post, comments ์ˆœ์œผ๋กœ ๋“ค์–ด๊ฐ€๋Š” ๋ฐฉ์‹์ด์ฃ .

Progressive JSON์€ ๋„ˆ๋น„ ์šฐ์„ (breadth-first) ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค:

{
  header: "$1",
  post: "$2", 
  footer: "$3"
}

$ ํ‘œ์‹œ๋Š” ์•„์ง ์ „์†ก๋˜์ง€ ์•Š์€ ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” placeholder์ž…๋‹ˆ๋‹ค. ์ดํ›„ ์ŠคํŠธ๋ฆผ์—์„œ ์ ์ง„์ ์œผ๋กœ ์ฑ„์›Œ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค:

{
  header: "$1",
  post: "$2",
  footer: "$3"
}
/*$1*/
"Welcome to my blog"
/*$3*/
"Hope you like it"

$1๊ณผ $3์€ ๋จผ์ € ๋ณด๋ƒˆ์ง€๋งŒ $2๋Š” ์•„์ง ๋Œ€๊ธฐ ์ค‘์ธ ์ƒํ™ฉ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํŠธ๋ฆฌ๋ฅผ ์žฌ๊ตฌ์„ฑํ•˜๋ฉด:

{
  header: "Welcome to my blog",
  post: new Promise(/* ... ์•„์ง ์ดํ–‰๋˜์ง€ ์•Š์Œ ... */),
  footer: "Hope you like it"
}

์•„์ง ๋กœ๋“œ๋˜์ง€ ์•Š์€ ๋ถ€๋ถ„๋“ค์€ Promise๋กœ ํ‘œํ˜„๋ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„๊ฐ€ ๊ณ„์† ์ŠคํŠธ๋ฆฌ๋ฐํ•˜๋ฉด:

{
  header: "$1",
  post: "$2",
  footer: "$3"
}
/*$1*/
"Welcome to my blog"
/*$3*/
"Hope you like it"
/*$2*/
{
  content: "$4",
  comments: "$5"
}
/*$4*/
"This is my article"
/*$5*/
["$6", "$7", "$8"]
/*$6*/
"This is the first comment"
/*$7*/
"This is the second comment"
/*$8*/
"This is the third comment"

์ตœ์ข…์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์™„์ „ํ•œ ํŠธ๋ฆฌ๊ฐ€ ์กฐ๋ฆฝ๋ฉ๋‹ˆ๋‹ค:

{
  header: "Welcome to my blog",
  post: {
    content: "This is my article",
    comments: [
      "This is the first comment",
      "This is the second comment", 
      "This is the third comment"
    ]
  },
  footer: "Hope you like it"
}

๋ฐ์ดํ„ฐ๋ฅผ ๋„ˆ๋น„ ์šฐ์„  ๋ฐฉ์‹์œผ๋กœ ์ฒญํฌ ๋‹จ์œ„๋กœ ์ „์†กํ•จ์œผ๋กœ์จ, ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์ด๋ฅผ ์ ์ง„์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

3. Inlining: ์„ ํƒ์  ์ง€์—ฐ ์ „์†ก โœ๏ธ

์ธ๋ผ์ด๋‹์€ ๋А๋ฆฌ๊ฒŒ ์ƒ์„ฑ๋˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์ง€์—ฐ ๋กœ๋”ฉํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ์ฆ‰์‹œ ํฌํ•จ์‹œ์ผœ ์ „์†กํ•˜๋Š” ์„ ํƒ์  ์ง€์—ฐ ์ „์†ก ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค.

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

post ๋ณธ๋ฌธ๊ณผ ๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ž‘์—…๋งŒ ํŠนํžˆ ๋А๋ฆฐ ์ƒํ™ฉ์ด๋ผ๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค:

1๋‹จ๊ณ„: ๊ฐ€์žฅ ๋ฐ”๊นฅ ์š”์†Œ๋ฅผ ๋จผ์ € ์ „์†ก

{
  header: "Welcome to my blog",
  post: "$1",
  footer: "Hope you like it"
}

ํด๋ผ์ด์–ธํŠธ ํ•ด์„:

{
  header: "Welcome to my blog",
  post: new Promise(/* ... ์•„์ง ์ดํ–‰๋˜์ง€ ์•Š์Œ ... */),
  footer: "Hope you like it"
}

2๋‹จ๊ณ„: comments๋ฅผ ์ œ์™ธํ•œ post ๋ฐ์ดํ„ฐ ์ „์†ก

{
  header: "Welcome to my blog",
  post: "$1",
  footer: "Hope you like it"
}
/*$1*/
{
  content: "This is my article",
  comments: "$2"
}

ํด๋ผ์ด์–ธํŠธ ํ•ด์„:

{
  header: "Welcome to my blog",
  post: {
    content: "This is my article",
    comments: new Promise(/* ... ์•„์ง ์ดํ–‰๋˜์ง€ ์•Š์Œ ... */),
  },
  footer: "Hope you like it"
}

3๋‹จ๊ณ„: ๋Œ“๊ธ€์„ ํ•œ ๋ฒˆ์— ๋ฌถ์–ด์„œ ์ „์†ก

/*$2*/
[
  "This is the first comment",
  "This is the second comment", 
  "This is the third comment"
]

์ธ๋ผ์ด๋‹์˜ ํ•ต์‹ฌ์€ ์†๋„์™€ ํ•„์š”์— ๋”ฐ๋ฅธ ๊ท ํ˜• ์žˆ๋Š” ๋ฐ์ดํ„ฐ ๋ถ„ํ•ด์ž…๋‹ˆ๋‹ค. ๋А๋ฆฐ ๋ถ€๋ถ„๋งŒ ๋ณ„๋„๋กœ ์ง€์—ฐ์‹œํ‚ค๊ณ , ๋น ๋ฅด๊ฒŒ ์ค€๋น„๋˜๋Š” ๋ถ€๋ถ„์€ ์ฆ‰์‹œ ํฌํ•จ์‹œ์ผœ ์ „์†กํ•จ์œผ๋กœ์จ ์ „์ฒด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋งค๋„๋Ÿฝ๊ฒŒ ๋งŒ๋“œ๋Š” ์ „๋žต์ž…๋‹ˆ๋‹ค.

4. Outlining: ์ค‘๋ณต ์ œ๊ฑฐ์™€ ์ˆœํ™˜ ์ฐธ์กฐ ์ง€์› โœ๏ธ

์•„์›ƒ๋ผ์ด๋‹์€ ์ค‘๋ณต๋˜๊ฑฐ๋‚˜ ์ฐธ์กฐ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ† ํฐ์œผ๋กœ ๋Œ€์ฒดํ•œ ํ›„, ๊ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ„๋„์˜ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์ˆœ์ฐจ์ ์œผ๋กœ ์ „์†กํ•จ์œผ๋กœ์จ ์ค‘๋ณต ์ œ๊ฑฐ์™€ ์ˆœํ™˜ ์ฐธ์กฐ ์ง€์›์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” '์ „๋ฉด' ์ง€์—ฐ ์ „์†ก ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฐ์ฒด ํŠธ๋ฆฌ๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ด…์‹œ๋‹ค:

const userInfo = { name: 'Dan' };

[
  { type: 'header', user: userInfo },
  { type: 'sidebar', user: userInfo },
  { type: 'footer', user: userInfo }
]

์ผ๋ฐ˜ JSON์œผ๋กœ ์ง๋ ฌํ™”ํ•˜๋ฉด { name: 'Dan' }์ด ๋ฐ˜๋ณต๋ฉ๋‹ˆ๋‹ค:

[
  { type: 'header', user: { name: 'Dan' } },
  { type: 'sidebar', user: { name: 'Dan' } },
  { type: 'footer', user: { name: 'Dan' } }
]

Progressive JSON์—์„œ๋Š” ์•„์›ƒ๋ผ์ธ์œผ๋กœ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

[
  { type: 'header', user: "$1" },
  { type: 'sidebar', user: "$1" },
  { type: 'footer', user: "$1" }
]
/* $1 */
{ name: "Dan" }

์•„์›ƒ๋ผ์ด๋‹์˜ ์žฅ์ :

  • ์ค‘๋ณต ์ œ๊ฑฐ: ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ „์†กํ•˜์ง€ ์•Š์Œ
  • ์ˆœํ™˜ ์ฐธ์กฐ ์ง€์›: ์ผ๋ฐ˜ JSON๊ณผ ๋‹ฌ๋ฆฌ ์ˆœํ™˜ ๊ฐ์ฒด๋„ ์ง๋ ฌํ™” ๊ฐ€๋Šฅ
  • ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ: ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ์ฐธ์กฐํ•  ๋•Œ ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ

์ธ๋ผ์ด๋‹๊ณผ ์•„์›ƒ๋ผ์ด๋‹์„ ํ˜ผํ•ฉํ•˜์—ฌ ๋” ๊ท ํ˜• ์žกํžŒ ์ „๋žต์„ ์ทจํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์ธ๋ผ์ธ์œผ๋กœ ์ง๋ ฌํ™”ํ•˜๋˜, ๊ฐ™์€ ๊ฐ์ฒด๊ฐ€ ๋‘ ๋ฒˆ ์ด์ƒ ๋“ฑ์žฅํ•˜๋ฉด ๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

5. Streaming Data vs Streaming UI โœ๏ธ

์ง€๊ธˆ๊นŒ์ง€์˜ ์„ค๋ช…์€ ์‚ฌ์‹ค ๋ฆฌ์•กํŠธ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ(React Server Components)๊ฐ€ ๋™์ž‘ํ•˜๋Š” ๋ฐฉ์‹๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

๋ฆฌ์•กํŠธ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ ๋‹ค๊ณ  ํ•ด๋ด…์‹œ๋‹ค:

function Page() {
  return (
    <html>
      <body>
        <header>Welcome to my blog</header>
        <Post />
        <footer>Hope you like it</footer>
      </body>
    </html>
  );
}

async function Post() {
  const post = await loadPost();
  return (
    <article>
      <p>{post.text}</p>
      <Comments />
    </article>
  );
}

async function Comments() {
  const comments = await loadComments();
  return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>;
}

๋ฆฌ์•กํŠธ๋Š” Page์˜ ๊ฒฐ๊ณผ๋ฅผ Progressive JSON ์ŠคํŠธ๋ฆผ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ์— ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ํ•ด๋‹น ์ŠคํŠธ๋ฆผ์ด ์ ์ง„์ ์œผ๋กœ ๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ๋กœ ๋ณต์›๋ฉ๋‹ˆ๋‹ค.

๋กœ๋”ฉ ๋‹จ๊ณ„๋ณ„ UI ์ƒํƒœ:

1๋‹จ๊ณ„: ์ดˆ๊ธฐ ์ƒํƒœ

<html>
  <body>
    <header>Welcome to my blog</header>
    {new Promise(/* ... ์•„์ง ์ดํ–‰๋˜์ง€ ์•Š์Œ */)}
    <footer>Hope you like it</footer>
  </body>
</html>

2๋‹จ๊ณ„: loadPost() ์™„๋ฃŒ ํ›„

<html>
  <body>
    <header>Welcome to my blog</header>
    <article>
      <p>This is my post</p>
      {new Promise(/* ... ์•„์ง ์ดํ–‰๋˜์ง€ ์•Š์Œ */)}
    </article>
    <footer>Hope you like it</footer>
  </body>
</html>

3๋‹จ๊ณ„: loadComments() ์™„๋ฃŒ ํ›„

<html>
  <body>
    <header>Welcome to my blog</header>
    <article>
      <p>This is my post</p>
      <ul>
        <li key="1">This is the first comment</li>
        <li key="2">This is the second comment</li>
        <li key="3">This is the third comment</li>
      </ul>
    </article>
    <footer>Hope you like it</footer>
  </body>
</html>

Suspense๋ฅผ ํ†ตํ•œ UI ์ œ์–ด

๋ฐ์ดํ„ฐ๊ฐ€ ์ ์ง„์ ์œผ๋กœ ๋“ค์–ด์˜ค๋”๋ผ๋„, ํŽ˜์ด์ง€๊ฐ€ ์ž„์˜๋กœ ์ ํ”„ํ•˜๋“ฏ ๋ Œ๋”๋ง๋˜๊ธธ ์›ํ•˜์ง€๋Š” ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ํฌ์ŠคํŠธ ๋ณธ๋ฌธ ์—†์ด ํ—ค๋”์™€ ํ‘ธํ„ฐ๋งŒ ๋จผ์ € ๋ณด์ด๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š๊ฒ ์ฃ .

๋ฆฌ์•กํŠธ๋Š” <Suspense>๋ฅผ ํ†ตํ•ด ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค:

import { Suspense } from 'react';

function Page() {
  return (
    <html>
      <body>
        <header>Welcome to my blog</header>
        <Post />
        <footer>Hope you like it</footer>
      </body>
    </html>
  );
}

async function Post() {
  const post = await loadPost();
  return (
    <article>
      <p>{post.text}</p>
      <Suspense fallback={<CommentsGlimmer />}>
        <Comments />
      </Suspense>
    </article>
  );
}

async function Comments() {
  const comments = await loadComments();
  return <ul>{comments.map(c => <li key={c.id}>{c.text}</li>)}</ul>;
}

์‚ฌ์šฉ์ž๋Š” ์ด์ œ ๋‘ ๋‹จ๊ณ„์— ๊ฑธ์นœ ๋กœ๋”ฉ ์‹œํ€€์Šค๋ฅผ ๊ฒฝํ—˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค:

  1. 1๋‹จ๊ณ„: ํฌ์ŠคํŠธ ๋ณธ๋ฌธ / ํ—ค๋” / ํ‘ธํ„ฐ / ๋Œ“๊ธ€์˜ placeholder๊ฐ€ ํ•จ๊ป˜ ๋‚˜ํƒ€๋‚จ
  2. 2๋‹จ๊ณ„: ๋Œ“๊ธ€์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋“ค์–ด์˜ด

๋ฆฌ์•กํŠธ ํŠธ๋ฆฌ ์•ˆ์˜ Promise๊ฐ€ throw๋ผ๋ฉด, Suspense๋Š” catch์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋Š” ์„œ๋ฒ„๊ฐ€ ์ค€๋น„๋˜๋Š” ๋Œ€๋กœ ๊ฐ€๋Šฅํ•œ ํ•œ ๋นจ๋ฆฌ ๋„์ฐฉํ•˜์ง€๋งŒ, ๋ฆฌ์•กํŠธ๋Š” ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ ์šฐ์•„ํ•˜๊ฒŒ ๋ณด์—ฌ์ฃผ๋Š” ํ๋ฆ„์„ ๊ด€๋ฆฌํ•ด์ฃผ๊ณ , ๊ฐœ๋ฐœ์ž๊ฐ€ ์‹œ๊ฐ์ ์ธ ๊ณต๊ฐœ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

6. Conclusion โœ๏ธ

Progressive JSON์€ ๋‹จ์ˆœํžˆ ๊ธฐ์ˆ ์  ์ตœ์ ํ™”๋ฅผ ๋„˜์–ด์„œ, ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„์„ ์˜๋ฏธ ์žˆ๊ฒŒ ๋งŒ๋“œ๋Š” ์ฒ ํ•™์„ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋Œ€์‹ , ์ค€๋น„๋œ ๊ฒƒ๋ถ€ํ„ฐ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ณด์—ฌ์ฃผ๋ฉฐ ์ „์ฒด์ ์ธ ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ Progressive JSON์˜ ํ•ต์‹ฌ ๊ฐ€์น˜์ž…๋‹ˆ๋‹ค.

reference
: https://overreacted.io/progressive-json/#streaming-data-vs-streaming-ui
: https://hanameee.github.io/posts/progressive_json?utm_source=substack&utm_medium=email
: https://www.liquidweb.com/blog/what-is-a-progressive-jpeg/

profile
Write a little every day, without hope, without despair โœ๏ธ

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