๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐ŸŒฑ 26๋…„ 2์›” ํšŒ๊ณ 

์•ฝ 18๋ถ„
Ju young Lee
A contribution-driven developer

๋“ค์–ด๊ฐ€๋ฉฐโ€‹

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

AI ๋•๋ถ„์— ๋ณธ์งˆ์„ ๋น ๋ฅด๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์œ ์šฉํ–ˆ์ง€๋งŒ, ๋ฌธ์ œ์˜ ๋ณธ์งˆ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ ๊ฒŒ ๋ณธ์งˆ์ด ์•„๋‹ ์ˆ˜ ์žˆ๊ธฐ์— ๊ณ„์† ๊ณต๋ถ€ํ•ด์•ผ ํ•จ์„ ๋А๋‚€ ํ•œ ๋‹ฌ์ด์—ˆ๋‹ค.

๋Œ์ด์ผœ๋ณด๋ฉด, ๊ทธ ๊ณผ์ •์—์„œ์˜ ๊ณ ๋ฏผ๋“ค์ด 2์›”์˜ ๊ฐ€์žฅ ํฐ ์ž์‚ฐ์ด ๋œ ๊ฒƒ ๊ฐ™๋‹ค. 2์›”์—๋Š” ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€ ์‚ฌ๊ฑด๋“ค์ด ์žˆ์—ˆ๋‹ค.

  1. ์„ค๋‚  ๊ฒฐ์ œ ์žฅ์•  ๋Œ€์‘ โ€” ์‚ฌํŒŒ๋ฆฌ์—์„œ ๊ฒฐ์ œ ์™„๋ฃŒ ํŽ˜์ด์ง€๊ฐ€ ํฐ ํ™”๋ฉด์œผ๋กœ ๋œจ๋Š” ๋ฌธ์ œ๋ฅผ ๊ธด๊ธ‰ ๋Œ€์‘
  2. ์›น-์ง€๋„ ์ธ์ฆ ์—ฐ๋™ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… โ€” ์‚ฌํŒŒ๋ฆฌ/ํฌ๋กฌ์—์„œ ํšŒ์›๊ฐ€์ž… ํ›„ ์ง€๋„์— ์ธ์ฆ์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋˜ ๋ฌธ์ œ
  3. UI ์ œ์•ˆ์œผ๋กœ ๋ฌธ์˜์œจ ๊ฐ์†Œ โ€” ๊ฐœ๋ฐœ์ž๊ฐ€ ๋จผ์ € ๊ณ ๊ฐ ๋ถˆํŽธ์„ ๋ฐœ๊ฒฌํ•˜๊ณ , ์ž‘์€ UI ๋ณ€๊ฒฝ์œผ๋กœ ์›น ๋ฌธ์˜๋ฅผ ๊ฐ์†Œ์‹œํ‚จ ๊ฒฝํ—˜

๋จผ์ € 3๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ๋” ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ์˜คํ”ˆ๋‹ฅํ„ฐ์›น์˜ ํ˜„์žฌ ์ƒํ™ฉ์„ ์•Œ์•„๋ณด๊ณ  3๊ฐ€์ง€์˜ ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ ๊ณผ์ •์„ ์‚ดํŽด๋ณด๊ณ  ๋งˆ๋ฌด๋ฆฌํ•˜๊ณ ์ž ํ•œ๋‹ค.


์˜คํ”ˆ ๋‹ฅํ„ฐ ์›น์˜ ํ˜„์žฌ ์ƒํ™ฉโ€‹

์˜คํ”ˆ๋‹ฅํ„ฐ ์›น์€ Flutter-web์—์„œ Next.js๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ค‘์— ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋“ค์ด์—ˆ๋‹ค.

ํ˜„์žฌ ๊ตฌ์กฐ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๋ฉด ์ด๋ ‡๋‹ค.

  1. Next.js๊ฐ€ ๋ถ€๋ชจ iframe์œผ๋กœ ๋ฐ”๊นฅ์„ ๊ฐ์‹ธ๊ณ  ์žˆ๋‹ค.
  2. Next.js์˜ ์ž์‹ iframe์œผ๋กœ Flutter-web์ด ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค.
  3. Flutter-web์˜ ์ž์‹ iframe์œผ๋กœ React ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค.

img alt="migration"

์›๋ž˜๋Š” Flutter-web <-> react ์˜€๋Š”๋ฐ ์œ„์˜ ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด Next.js <-> Flutter-web <-> react ๋กœ ๋ณ€๊ฒฝ๋๋‹ค.

์ด ๊ตฌ์กฐ๊ฐ€ ์ฃผ๋Š” ์ด์ ์€ SEO๋ฅผ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ฑ™๊ธธ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค. ๋„๋ฉ”์ธ ๋ณ„๋กœ ์กฐ๊ธˆ์”ฉ Next.js๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๊ฐˆ์•„๋ผ์šฐ๋ฉด ๋˜๋Š” ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋Œ€์‹  ์–ด๋ ค์›€๋„ ์กด์žฌํ–ˆ๋‹ค. ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค ๋•Œ๋„ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๊ณ ๋ คํ•ด์•ผ ํ•  ๊ฒŒ ๋งŽ์•˜๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ธŒ๋ผ์šฐ์ €์˜ ํžˆ์Šคํ† ๋ฆฌ ์Šคํƒ ๋ฐ ์ƒํ˜ธ ์ž‘์šฉ์ด๋‹ค. ๋ฌด์—‡๋ณด๋‹ค... ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ง• ์ด์Šˆ์˜€๋‹ค. iframe๊ณผ PostMessage API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด ํ™˜๊ฒฝ์„ ๊ตฌ์ถ•ํ–ˆ๋Š”๋ฐ Safari์—์„  ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด๋‚ด์ง€์ง€ ์•Š๋Š”๋‹ค๊ฑฐ๋‚˜?! ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๊ฐ€ ์žฆ์•˜๋‹ค.

Safari๋Š” ITP(Intelligent Tracking Prevention) ๊ฐ™์€ ์ •์ฑ…์œผ๋กœ iframe ๋‚ด๋ถ€์˜ ์Šคํ† ๋ฆฌ์ง€ยท์ฟ ํ‚คยท๋„ค๋น„๊ฒŒ์ด์…˜์„ ํฌ๋กฌ๋ณด๋‹ค ํ›จ์”ฌ ์—„๊ฒฉํ•˜๊ฒŒ ์ œ์–ดํ•˜๋Š” ๊ฒƒ์„ ์•Œ์•˜๋‹ค. ๋˜‘๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ํฌ๋กฌ์—์„  ๋ฉ€์ฉกํžˆ ๋Œ๋‹ค๊ฐ€ ์‚ฌํŒŒ๋ฆฌ์—์„  ์กฐ์šฉํžˆ ๋ฉˆ์ถ”๋Š” ์ผ์ด ์ด ๊ฒฝ๊ณ„์—์„œ ์ƒ๊ฒผ๋‹ค... ์ด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ๊ฐ€์žฅ ํ•ด๊ฒฐํ•˜๊ธฐ ๋‚œ๊ฐํ–ˆ๊ณ  ์•„๋ž˜์—์„œ ์‚ดํŽด๋ณผ ๋ฌธ์ œ๋“ค์˜ ์ฃผ๋œ ์›์ธ์ด ๋˜์—ˆ๋‹ค.

์ด์ œ 3๊ฐ€์ง€ ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ ๊ณผ์ •์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ดํŽด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.


1. ์„ค๋‚ ์— ๊ฐ‘์ž๊ธฐ ๋ฆฌํฌํŠธ ๊ฒฐ์ œ๊ฐ€ ์•ˆ๋œ๋‹ค๋Š” ๋ฌธ์˜๊ฐ€!?โ€‹

๋จผ์ € ์˜คํ”ˆ๋‹ฅํ„ฐ์˜ ๋ฆฌํฌํŠธ๋Š” ๋ฌด์—‡์ธ๊ฐ€? ์‚ดํŽด๋ณด๊ณ ์ž ํ•œ๋‹ค. ๋งŽ์€ ๊ด€์‹ฌ ๋ถ€ํƒ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

img alt=&quot;report&quot;

์˜คํ”ˆ๋‹ฅํ„ฐ ๋ฆฌํฌํŠธ ์„œ๋น„์Šค๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์•Œ๊ธธ ์›ํ•˜๋Š” ์ž…์ง€์˜ ์˜์› ์ •๋ณด๋ฅผ ์กฐํ•ฉํ•ด ๊ฐœ์›์— ๋„์›€์ด ๋˜๋Š” ์ธ์‚ฌ์ดํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ์˜คํ”ˆ๋‹ฅํ„ฐ์›น์˜ ์œ ๋ฃŒ ์„œ๋น„์Šค๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ•˜ํ•„ ์„ค๋‚  ์—ฐํœด์— "๋ฆฌํฌํŠธ๋ฅผ ๊ฒฐ์ œํ–ˆ๋Š”๋ฐ ์•ˆ ๋ณด์ธ๋‹ค"๋Š” ๋ฌธ์˜๊ฐ€ ๋น—๋ฐœ์ณค๋‹ค. ์•„๋ฌดํŠผ ๊ธด๊ธ‰ ๋Œ€์‘์ด ํ•„์š”ํ–ˆ๊ณ , ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ๋Œ€์‘ํ–ˆ๋‹ค.

img alt=&quot;customer-reception&quot;

  1. ๊ฒฐ์ œํ–ˆ์ง€๋งŒ ๋ฆฌํฌํŠธ๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š” ๊ณ ๊ฐ์€ ํ™˜๋ถˆ ์ฒ˜๋ฆฌ๋ฅผ ๋„์™€๋“œ๋ ธ๋‹ค.
  2. ๊ฒฐ์ œํ–ˆ์ง€๋งŒ ๋ฆฌํฌํŠธ๊ฐ€ ๋ณด์ด์ง€ ์•Š๋Š” ๊ณ ๊ฐ ์ค‘ ๋ฐ˜๋“œ์‹œ ๋ฆฌํฌํŠธ๋ฅผ ๋ด์•ผ๋งŒ ํ•˜๋Š” ๊ณ ๊ฐ์€ ์–ด๋“œ๋ฏผ ๊ถŒํ•œ์œผ๋กœ ์˜คํ”ˆ๋‹ฅํ„ฐ ์›น์— ์ ‘๊ทผํ•ด ํ•ด๋‹น ์ง€์—ญ ๋ฆฌํฌํŠธ๋ฅผ PDF๋กœ ๋ฝ‘์•„ ๋ฉ”์ผ๋กœ ์ง์ ‘ ์ „๋‹ฌ

ํ•ด๋‹น ์›์ธ ํŒŒ์•…โ€‹

ํ•ด๋‹น ๋ฌธ์ œ๋Š” ๋ฆฌํฌํŠธ ์ƒ์„ธ์—์„œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ดํ›„ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์˜€๋‹ค. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์˜ ๋ถ€์ž‘์šฉ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜๋„ ์žˆ๋Š”๋ฐ ์ด๋Ÿฐ ๋ถ€๋ถ„์ด ์–ด๋ ค์› ๋‹ค... ์•„๋ฌด๋ฆฌ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๊ฐ€ ์žˆ์–ด๋„ ์ปค๋ฒ„ํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š์•˜๋‹ค. ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๊ผผ๊ผผํžˆ ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜๋˜ ๋‚ด๊ฐ€ ๋ฌธ์ œ์˜€์„ ์ˆ˜ ์žˆ๊ณ  ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ์™ธ๋ถ€ SDK๋ฅผ ๊ฒ€์ฆํ•˜๋Š”๊ฒŒ ์–ด๋ ค์šด ๊ฒƒ์ผ ์ˆ˜ ์žˆ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ ๋‹ค.

๋ณ€๊ฒฝ๋œ UI๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • ASIS (Flutter-web ๋ฆฌํฌํŠธ ์ƒ์„ธ) img alt=&quot;m-report-asis&quot;
  • TOBE (Next.js ๋ฆฌํฌํŠธ ์ƒ์„ธ) img alt=&quot;m-report-tobe&quot;

๊ธ‰ํ•œ ๋งˆ์Œ์— ์šฐ์„  ์•„๋ž˜์˜ ํ•œ ์ค„์„ ํ”Œ๋Ÿฌํ„ฐ์›น ์ฝ”๋“œ์—์„œ ์ถ”๊ฐ€ํ•ด์„œ ๊ฐ•์ œ๋กœ ํ•ด๋‹น ๊ฒฐ์ œ ์™„๋ฃŒ ํŽ˜์ด์ง€๋กœ ๋ฐ˜๋“œ์‹œ ์ด๋™ํ•˜๊ฒŒ ํ–ˆ๋‹ค.

window.parent.postMessage({ type: "paymentResult", ... }, "*");
window.location.href = uri; // ๊ฐ•์ œ๋กœ ํŽ˜์ด์ง€ ์ด๋™ํ•˜๋Š” ๋กœ์ง์„ ํ”Œ๋Ÿฌํ„ฐ์—์„œ ์ถ”๊ฐ€! ๊ธฐ์กด์—๋Š” Next.js์—์„œ ํŽ˜์ด์ง€ ์ด๋™ ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰

๊ทธ๋Ÿฐ๋ฐ ํšŒ๊ณ ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ์ปค๋ฐ‹ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ๋‹ค์‹œ ๊บผ๋‚ด๋ณด๋‹ˆ, ๊ตฌ์กฐ์ ์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ์ด๋™์‹œํ‚ค๋Š” ์ฃผ์ฒด์˜ ๋ณ€๊ฒฝ์œผ๋กœ ํ•ด๊ฒฐํ•œ ๋ฌธ์ œ์˜€๋‹ค.

  • ์ž์‹(Flutter iamport.js): ๊ฒฐ์ œ ์„ฑ๊ณต โ†’ window.parent.postMessage๋กœ ๋ถ€๋ชจ์— ๊ฒฐ๊ณผ๋งŒ ์ „๋‹ฌ.
  • ๋ถ€๋ชจ(Next.js): ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  โ†’ router.push('/payment/complete?...')๋กœ ๊ฒฐ์ œ ์™„๋ฃŒ ํŽ˜์ด์ง€ ๋„ค๋น„๊ฒŒ์ด์…˜

ํŽ˜์ด์ง€ ์ด๋™ ๋กœ์ง์˜ ์ฑ…์ž„์€ ๋ถ€๋ชจ(Next.js)๊ฐ€ ๊ฐ–๊ณ  ์žˆ์—ˆ๋Š”๋ฐ, ์ž์‹์ด์—ˆ๋˜ ํ”Œ๋Ÿฌํ„ฐ์›น์—์„œ๋„ ๊ฒฐ์ œ ๋ถ€๋ถ„์—์„  ์ด๋™ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

Flutter ์ธก iamport.js โ€” ์ž์‹์ด ์Šค์Šค๋กœ ์ด๋™ํ•˜๋Š” ๋กœ์ง ์ถ”๊ฐ€:

window.parent.postMessage({ type: "paymentResult", ... }, "*");
// Flutter ๊ฒฐ์ œ ์™„๋ฃŒ ํŽ˜์ด์ง€๋กœ ์ด๋™ (WebTabIamportResult โ†’ POST /report/region/payment ํ˜ธ์ถœ)
window.location.href = uri;

Next.js ์ธก ์˜ค๋ฒ„๋ ˆ์ด ํ›… โ€” ๋ถ€๋ชจ๊ฐ€ ํ•˜๋˜ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋กœ์ง ์ œ๊ฑฐ:

- router.push(`/payment/complete?${query.toString()}`);
- router.refresh();
+ // Flutter๊ฐ€ iamport.js์—์„œ ์ง์ ‘ /payment/complete ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ฏ€๋กœ
+ // Next.js๋Š” ๊ฒฐ์ œ ๋ชจ๋‹ฌ ์˜ค๋ฒ„๋ ˆ์ด๋งŒ ๋‹ซ์•„์คŒ

์กฐ๊ธˆ ๋” ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„  ์„œ๋ฒ„๊ฐ€ "๊ฒฐ์ œ๋๋‹ค"๋ฅผ ์•Œ๊ฒŒ ๋˜๋Š” ํƒ€์ด๋ฐ์„ ๋ด์•ผ ํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ์ด์œ ๋Š” ์„œ๋ฒ„์—์„œ ๊ฒฐ์ œ ์™„๋ฃŒ๋ฅผ ์•Œ์•„์ฐจ๋ฆฌ๋Š” ํƒ€์ด๋ฐ์ด Flutter ์ชฝ WebTabIamportResult ์œ„์ ฏ์ด ํ™”๋ฉด์— ๋งˆ์šดํŠธ๋  ๋•Œ Riverpod (ํ”Œ๋Ÿฌํ„ฐ์˜ ์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, zustand๊ณผ ๊ฐ™๋‹ค) ์˜ postReportPaymentProvider๊ฐ€ ์‚ฌ์šฉ๋˜๊ณ , ์—ฌ๊ธฐ์„œ POST /report/region/payment๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค.

์ฆ‰ ์„œ๋ฒ„๊ฐ€ ๊ฒฐ์ œ ์™„๋ฃŒ๋ฅผ ์•Œ๊ฒŒ ๋˜๋Š” ์‹œ์ ์ด Flutter ์œ„์ ฏ์ด ํ™”๋ฉด์— ๋งˆ์šดํŠธ๋˜๋Š” ์ˆœ๊ฐ„์— ์ผ์–ด๋‚œ๋‹ค. ์œ„์ ฏ์ด ๋งˆ์šดํŠธ๋˜์ง€ ์•Š์œผ๋ฉด ์„œ๋ฒ„๋Š” ๊ฒฐ์ œ ์‚ฌ์‹ค์„ ๋ชจ๋ฅด๊ณ , ๊ณ ๊ฐ ์ž…์žฅ์—์„œ๋Š” ๋ˆ์€ ๋‚˜๊ฐ”๋Š”๋ฐ ๋ฆฌํฌํŠธ๊ฐ€ ์•ˆ ์—ด๋ฆฌ๋Š” ์ƒํ™ฉ์ด ๋œ๋‹ค.


2. ์˜คํ”ˆ๋‹ฅํ„ฐ ์›น์—์„œ ์ธ์ฆํ•˜๋ฉด ์ง€๋„์— ๋ฐ˜์˜์ด ์•ˆ ๋œ๋‹ค?โ€‹

๋‰ด์˜คํ”ˆ๋‹ฅํ„ฐ(Next.js)์— ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์„ ๋ฐฐํฌํ•œ ๋’ค, ๋ฉฐ์น  ํ›„ ๋ฐœ๊ฒฌ๋œ ๋ฌธ์ œ์˜€๋‹ค. Next.js์—์„œ ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ์›น์€ ์ •์ƒ์ธ๋ฐ, Flutter-web ์ง€๋„์—๋Š” ์ธ์ฆ ์ƒํƒœ๊ฐ€ ๋ฐ˜์˜๋˜์ง€ ์•Š์•˜๋‹ค. ๋ถ„๋ช… QA๋ฅผ ํ–ˆ๋Š”๋ฐ ๋†“์ณค๋‹ค... ์•„์‰ฌ์šด ๋งˆ์Œ์ด ์ปธ๋‹ค.

๋ถ€๋ชจ-์ž์‹ ์‚ฌ์ด ์ธ์ฆ ์ƒํƒœ ๋™๊ธฐํ™”๊ฐ€ ๋น ์ ธ ์žˆ์—ˆ๋‹ค.โ€‹

Next.js(๋ถ€๋ชจ)์—์„œ ๋กœ๊ทธ์ธ์ด ์ผ์–ด๋‚˜๋„, Flutter-web(์ž์‹ iframe)์ด ๊ทธ ์‚ฌ์‹ค์„ ์•Œ์•„์•ผ ์ง€๋„๊ฐ€ ์ธ์ฆ ์ƒํƒœ๋กœ ๋™์ž‘ํ•œ๋‹ค. ์ด ๋กœ์ง์ด ๋ถ„๋ช…ํžˆ ์žˆ๊ธด ํ–ˆ๋Š”๋ฐ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์•˜๋‹ค.

ํ•ด๋‹น ๋ฌธ์ œ์˜ ์›์ธ์„ ๋„์‹ํ™” ํ•ด๋ณด์•˜๋‹ค.

img alt=&quot;m-auth-token-error&quot;

๊ทผ๋ณธ ์›์ธ: Safari iframe์—์„œ FlutterSecureStorage๊ฐ€ ๋ฉˆ์ถ”๋Š” ๊ฒŒ ์›์ธ์ด์—ˆ๋‹ค.โ€‹

๋””๋ฒ„๊น… ๊ณผ์ •์—์„œ ์•Œ๊ฒŒ ๋œ ์›์ธ์€ ์ด๋žฌ๋‹ค.

  1. Next.js์—์„œ ํšŒ์›๊ฐ€์ž… โ†’ postMessage๋กœ Flutter-web์— ํ† ํฐ ์ „๋‹ฌ
  2. Flutter-web์ด ํ† ํฐ์„ FlutterSecureStorage์— ์ €์žฅ ์‹œ๋„
  3. FlutterSecureStorage์˜ ์›น ๊ตฌํ˜„์ฒด๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ WebCrypto + IndexedDB๋ฅผ ์‚ฌ์šฉ
  4. Safari์˜ ITP๊ฐ€ iframe ๋‚ด third-party IndexedDB ์ ‘๊ทผ์„ ํŒŒํ‹ฐ์…”๋‹ ์ฒ˜๋ฆฌ
  5. crypto.subtle ์ ‘๊ทผ์ด ์ฐจ๋‹จ๋˜๋ฉด Promise๊ฐ€ resolve/reject ์—†์ด ๋ฌดํ•œ ๋Œ€๊ธฐ(hang)
  6. ๊ฒฐ๊ณผ: ํ† ํฐ ์ €์žฅ ์‹คํŒจ โ†’ API ํ˜ธ์ถœ ์‹œ ์ธ์ฆ ํ—ค๋” ์—†์Œ โ†’ ์ง€๋„์— ์ธ์ฆ ์ƒํƒœ ๋ฏธ๋ฐ˜์˜

ํฌ๋กฌ์€ iframe ๋‚ด third-party storage ์ ‘๊ทผ์„ Safari๋งŒํผ ์—„๊ฒฉํžˆ ์ œํ•œํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ํฌ๋กฌ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ์žฌํ˜„๋˜์ง€ ์•Š์•˜๋‹ค.

ํ•ด๊ฒฐ: FlutterSecureStorage โ†’ ์ธ๋ฉ”๋ชจ๋ฆฌ ํ† ํฐ์œผ๋กœ ์ „ํ™˜โ€‹

img alt=&quot;m-auth-token-solve&quot;

๋ชจ๋“  ๊ณณ์—์„œ FlutterSecureStorage ์ง์ ‘ ์ฝ๊ธฐ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , AuthManager().opn_token์— ์ธ๋ฉ”๋ชจ๋ฆฌ๋กœ ์ €์žฅ/์ฝ๊ธฐํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ๋‹ค.

// Before: Safari iframe์—์„œ hang ๋ฐœ์ƒ
const storage = FlutterSecureStorage();
var opnToken = await storage.read(key: "OPN_OPNDOCTOR_KEY_AUTH_OPN_TOKEN");

// After: ์ธ๋ฉ”๋ชจ๋ฆฌ ํ† ํฐ ์‚ฌ์šฉ (Safari iframe์—์„œ FlutterSecureStorage๊ฐ€ ๋ฉˆ์ถœ ์ˆ˜ ์žˆ์–ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ)
var opnToken = AuthManager().opn_token;

๋™์‹œ์— Next.js ์ชฝ์—์„œ๋„ Flutter โ†” Next.js ๊ฐ„ ์ธ์ฆ ์ƒํƒœ ์–‘๋ฐฉํ–ฅ ๋™๊ธฐํ™”๋ฅผ ์œ„ํ•œ ๋ฉ”์‹œ์ง€ ํ•ธ๋“ค๋Ÿฌ(useAuthMessageHandlers)์™€ ์„ธ์…˜ ๊ด€๋ฆฌ ํฌ์ธํŠธ๋ฅผ ํ•œ ๊ณณ์— ๋ชจ์•„ ๊ด€๋ฆฌํ•˜๋„๋ก ํ–ˆ๋‹ค.


3. ๊ฐ„๋‹จํ•œ UI/UX ๊ฐœ์„  ์‹œ๋„โ€‹

๊ณ ๊ฐ ์ž…์žฅ์—์„œ ๋ถˆํŽธํ•œ ์ง€์ ์„ ๋ฐœ๊ฒฌํ•˜๋ฉด, ์‚ฌ์†Œํ•˜๋”๋ผ๋„ ์ œ์•ˆํ•˜๊ณ  ๊ณ ์น˜๋Š” ํƒœ๋„๊ฐ€ ์ค‘์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค. ์ œํ’ˆํŒ€ ๋ฆฌ๋”์ธ ์„ฑ์šฑ๋‹˜์€ ๋งˆ์Œ๊ป ํŒ€ ๋‚ด์— ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ๋ฐ UI/UX ๊ด€๋ จํ•ด์„œ ์ œ๋ณดํ•  ์ˆ˜ ์žˆ๋Š” ๋ถ„์œ„๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์…”์„œ ๋„ˆ๋ฌด ๊ฐ์‚ฌํ•˜๋‹ค.

2์›”์—๋Š” 2๊ฐ€์ง€ UI ๊ฐœ์„ ์„ ์ง์ ‘ ์ œ์•ˆํ–ˆ๋‹ค. 3/4์›”์—๋Š” ๋” ๋งŽ์ด ์ œ์•ˆํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ ์•ˆ๋‚ด ํˆดํŒ ํ•œ ์ค„์˜ ํšจ๊ณผโ€‹

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

img alt=&quot;error-signup&quot;

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋ˆ  ์ง„ํ–‰ํ–ˆ๋‹ค.

  1. ๊ทผ๋ณธ ํ•ด๊ฒฐ: ๋ฉดํ—ˆ ์ธ์ฆ API ์ œ๊ณต ์™ธ์ฃผ ์—…์ฒด์— ์˜ˆ์™ธ์ฒ˜๋ฆฌ ์ˆ˜์ •์„ ์š”์ฒญ
  2. ์ฆ‰์‹œ ์กฐ์น˜: ์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ ์ž…๋ ฅ๋ž€์— ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์„ ์•ˆ๋‚ดํ•˜๋Š” ํˆดํŒ ์ถ”๊ฐ€ โ€” ๋””์ž์ด๋„ˆ์™€ ๋…ผ์˜ํ•˜์—ฌ ๋ฐ”๋กœ ๋ฐ˜์˜

img alt=&quot;add-signup&quot;

์นด์นด์˜คํ†ก ๋ฌธ์˜ ๋น„๊ต (ํˆดํŒ ์ ์šฉ ์ „ vs ํ›„)โ€‹

ํšจ๊ณผ๋ฅผ ๊ณต์ •ํ•˜๊ฒŒ ๋ณด๋ ค๋ฉด ๊ฐ™์€ ์กฐ๊ฑด์—์„œ ๋น„๊ตํ•ด์•ผ ํ•œ๋‹ค. ์˜คํ”ˆ๋‹ฅํ„ฐ ์›น์˜ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์ด ํ”„๋กœ๋•์…˜์— ๋ฐฐํฌ๋œ ๊ฑด 1์›” 19์ผ, ํˆดํŒ์ด ๋ฐ˜์˜๋œ ๊ฑด 2์›” 13์ผ์ด๋‹ค. ์ด ๋‘ ์‹œ์ ์„ ๊ธฐ์ค€์œผ๋กœ ์นด์นด์˜คํ†ก ์ฑ„๋„ ์ƒ๋‹ด ๋ฐ์ดํ„ฐ๋ฅผ ์ž˜๋ผ๋ดค๋‹ค.

๊ตฌ๊ฐ„๊ธฐ๊ฐ„๊ด€๋ จ ๋ฌธ์˜ ๊ฑด์ˆ˜
ํˆดํŒ ์ ์šฉ ์ „1/19 ~ 2/12 (25์ผ)7๊ฑด
ํˆดํŒ ์ ์šฉ ํ›„2/13 ~ 3/9 (25์ผ)4๊ฑด

๊ฐ™์€ 25์ผ ๊ธฐ์ค€์œผ๋กœ ๋†“๊ณ  ๋ณด๋ฉด ์•ฝ 43% ๊ฐ์†Œํ–ˆ๋‹ค. ์ ˆ๋Œ€ ๊ฑด์ˆ˜๊ฐ€ ํฌ์ง„ ์•Š์ง€๋งŒ, ์™ธ์ฃผ ์—…์ฒด์˜ ๊ทผ๋ณธ ์ˆ˜์ •์ด ๋“ค์–ด์˜ค๊ธฐ ์ „ ์ž„์‹œ ์กฐ์น˜๋กœ์„œ๋Š” ์œ ์˜๋ฏธํ•œ ๊ฐ์†Œ์˜€๋‹ค๊ณ  ๋ณธ๋‹ค.

๋ฌธ์˜ ์ง‘๊ณ„ ์‹œ ์ฃผ์˜: ํ‚ค์›Œ๋“œ("๋ฉดํ—ˆ ์ธ์ฆ", "์ฃผ๋ฏผ๋“ฑ๋ก๋ฒˆํ˜ธ" ๋“ฑ)๋กœ ๊ด€๋ จ ๋ฌธ์˜๋งŒ ์ถ”๋ ธ๊ณ , ์„ค ์—ฐํœด ๊ธฐ๊ฐ„(2/8~2/10)์€ ๊ฒฐ์ œ ์žฅ์•  ๋ฌธ์˜๊ฐ€ ๊ฒน์ณ ์žˆ์–ด ๋ถ„์„ ์‹œ ์—ผ๋‘์— ๋’€๋‹ค.

๊ฒฐ๊ณผ์ ์œผ๋กœ 3์›”์—๋Š” ๋ฉดํ—ˆ ์ธ์ฆ ๊ด€๋ จํ•ด์„œ ๋ฌธ์˜๊ฐ€ ๋“ค์–ด์˜ค์ง€ ์•Š๊ฒŒ ๋๊ณ  ๋Œ€์‘ ์‹œ๊ฐ„์„ ์•„๋‚„ ์ˆ˜ ์žˆ๊ฒŒ ๋๋‹ค.

๋งค๋ฌผ ๋ชฉ๋ก ๋นˆ ์ƒํƒœ ๊ฐœ์„ โ€‹

์•Œ๊ฒŒ ๋œ ์นœ๊ตฌ์—๊ฒŒ ์˜คํ”ˆ๋‹ฅํ„ฐ๋ฅผ ๋ณด์—ฌ์ค„ ๊ธฐํšŒ๊ฐ€ ์žˆ์—ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์•Œ๊ณ  ๋ณด๋‹ˆ ์นœ๊ตฌ ์•„๋ฒ„์ง€๊ฐ€ ์˜์‚ฌ์…”์„œ ํ•จ๊ป˜ ์„œ๋น„์Šค๋ฅผ ๋‘˜๋Ÿฌ๋ณด๊ฒŒ ๋๋‹ค. ๊ทธ ์ƒํ™ฉ๋„ ์ง€๊ธˆ ์ƒ๊ฐํ•ด๋ณด๋ฉด ์›ƒ๊ธด ์ƒํ™ฉ์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ง€๋„์—์„œ ํŠน์ • ์œ„์น˜๋กœ ์ด๋™ํ–ˆ์„ ๋•Œ "์—๋Ÿฌ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค"๊ณ  ๋ง์”€ํ•˜์…จ๋‹ค... ๋“ฃ๊ณ  ์‹๊ฒํ–ˆ์—ˆ๋‹ค.

์•ฝ๊ฐ„ ๊ฒฝ์ง๋œ ์ƒํƒœ๋กœ ๋‹ค์‹œ ์‚ดํŽด๋ณด๋‹ˆ ์—๋Ÿฌ๋Š” ์•„๋‹ˆ์—ˆ๋‹ค. ํ•ด๋‹น ์œ„์น˜์— ๋งค๋ฌผ์ด ์—†์–ด์„œ "์ „์ฒด ๋งค๋ฌผ 0"์ด ํ‘œ์‹œ๋œ ๊ฒƒ๋ฟ์ด์—ˆ๋‹ค. ๊ธฐ๋Šฅ์ ์œผ๋กœ๋Š” ๋ฌธ์ œ๋Š” ์—†์ง€๋งŒ, ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ๋Š” ๋ญ”๊ฐ€ ์ž˜๋ชป๋œ ๊ฒƒ์ฒ˜๋Ÿผ ๋А๊ปด์ง€๋Š” ํ™”๋ฉด์ด์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‹จ์ˆœํžˆ ๋นˆ ์ƒํƒœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์—์„œ ํ•œ ๋ฐœ ๋” ๋‚˜์•„๊ฐ€, ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋งค๋ฌผ์ด ๊ฐ€์žฅ ๋งŽ์€ ์ง€์—ญ์ด๋‚˜ ์˜์›์ด ๋งŽ์€ ์œ„์น˜๋ฅผ ์ถ”์ฒœํ•ด์„œ ๋ฐ”๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ์•ˆํ–ˆ๊ณ , ๋ฐ˜์˜๋˜์–ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

img alt=&quot;no-property&quot;

๋ฐฑ์—”๋“œ API๋„ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค. ๋กœ์ง์€ ๋งค๋ฌผ ๋ฐ ์˜์›์ด ๊ฐ€์žฅ ๋งŽ์€ ๋™ 10๊ฐœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” API์˜€๋‹ค. ํ•ด๋‹น API์™€ ํ”„๋ก ํŠธ UI๋ฅผ ์—ฎ์–ด ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.


๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐโ€‹

2์›”์— ๋งŒ๋‚œ ์žฅ์• ๋“ค์€ ๋ชจ๋‘ iframe ์•ˆ์—์„œ ๋ถ€๋ชจ-์ž์‹ ์‚ฌ์ด์˜ ํ†ต์‹ ์— ๋”ฐ๋ฅธ ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ง• ์ด์Šˆ๋กœ ๋ฐœ์ƒํ–ˆ๊ณ , ์–ด๋ ดํ’‹์ด ์•Œ๋˜ ๊ฐœ๋…์„ ์‹ค์ œ ๋ฌธ์ œ๋ฅผ ๋งŒ๋‚˜ ์•Œ๊ฒŒ ๋œ ํ•œ ๋‹ฌ์ด์—ˆ๋‹ค. UI ๊ฐœ์„  ๊ฒฝํ—˜์€ ์ฝ”๋“œ ๋ฐ”๊นฅ์—์„œ๋„ ์ž‘์€ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ํ™•์ธํ•˜๊ฒŒ ํ•ด์คฌ๋‹ค. ํšŒ๊ณ ๊ฐ€ 4์›”๊นŒ์ง€ ๋ฐ€๋ ธ์ง€๋งŒ, ๊ทธ๋งŒํผ ์‹œ๊ฐ„์„ ๋‘๊ณ  ๋‹ค์‹œ ๋ณด๋‹ˆ ๋” ๋˜๋ ท์ด ๋‚จ๋Š” ์‚ฌ๊ฑด๋“ค์ด๋‹ค. 3์›”์—๋Š” ์˜คํ”ˆ๋‹ฅํ„ฐ ์›น ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์–ด๋–ป๊ฒŒ ๋งˆ๋ฌด๋ฆฌ์ง€์—ˆ๋Š”์ง€ ์ด์–ด์„œ ์ •๋ฆฌํ•ด๋ณด๋ ค ํ•œ๋‹ค.


โœจ์–ด๋–ค ๊ธฐ์—ฌ๋ฅผ ํ•ด์™”์„๊นŒ?