๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿ“ข ๋“ค์–ด๊ฐ€๊ธฐ ์ „์—

  • ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„  Nuxt.js ์˜ ๊ฐœ๋…๊ณผ ๊ตฌ์กฐ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ  ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณธ๋‹ค.

CSR vs SSR

Nuxt.js ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ธฐ์— ์•ž์„œ, SSR๊ณผ CSR์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.
์ด ๋‘๊ฐ€์ง€ ๊ฐœ๋…์€ Nuxt.js์˜ ๊ฐ€์žฅ ํฐ ํŠน์ง•์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

CSR (Client Side Rendering)

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง.
SPA(Single Page Application)์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

SPA๋ž€, ์ตœ์ดˆ ํ•œ๋ฒˆ ํŽ˜์ด์ง€๋ฅผ ์ „์ฒด ๋กœ๋”ฉํ•œ ํ›„ ๋ฐ์ดํ„ฐ๋งŒ ๋ณ€๊ฒฝํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งํ•œ๋‹ค.

 

์ตœ์ดˆ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋”ฉํ•œ ์‹œ์ ๋ถ€ํ„ฐ๋Š” ํŽ˜์ด์ง• ๋ฆฌ๋กœ๋”ฉ(๊นœ๋นก์ž„) ์—†์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์•„์„œ ํ™”๋ฉด์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋ Œ๋”๋ง ๋ฐฉ๋ฒ•์ด๋‹ค.

ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๊ฐฑ์‹ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŽ˜์ด์ง€ ์ด๋™์ด ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.

 

์„œ๋ฒ„์—์„œ View๋ฅผ ๋ Œ๋”ํ•˜์ง€ ์•Š๊ณ  ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ HTML์„ ๋‹ค์šด ๋ฐ›์€ ๋‹ค์Œ JS ํŒŒ์ผ์ด๋‚˜ ๊ฐ์ข… ๋ฆฌ์†Œ์Šค๋ฅผ ๋‹ค์šด ๋ฐ›์€ ํ›„ ๋ธŒ๋ผ์šฐ์ €์— ๋ Œ๋”๋งํ•˜์—ฌ ๋ณด์—ฌ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— SSR ๋ณด๋‹ค๋Š” ์ดˆ๊ธฐ View๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ๊นŒ์ง€ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค.

View๊ฐ€ ๋ณด์—ฌ์ง„ ์‹œ์ ์—์„œ ๋ฐ”๋กœ ์ธํ„ฐ๋ ‰์…˜(์ƒํ˜ธ์ž‘์šฉ)์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

๋Œ€๋ถ€๋ถ„์˜ ์›น ํฌ๋กค๋Ÿฌ, ๋ด‡๋“ค์ด JS ํŒŒ์ผ์„ ์‹คํ–‰์‹œํ‚ค์ง€ ๋ชปํ•˜๊ณ  HTML์—์„œ๋งŒ ์ปจํ…์ธ ๋ฅผ ์ˆ˜์ง‘ํ•œ๋‹ค. ๋•Œ๋ฌธ์— CSR ๋ฐฉ์‹ ํŽ˜์ด์ง€๋ฅผ ๋นˆ ํŽ˜์ด์ง€๋กœ ์ธ์‹ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด๋Š” ๊ฒ€์ƒ‰์—”์ง„์ด ์ œ๋Œ€๋กœ ๋…ธ์ถœ๋˜์ง€ ๋ชปํ•˜์—ฌ ์›นํŽ˜์ด์ง€ ์œ ์ž…์ด ์ค„์–ด๋“ค๊ฒŒ ๋˜๋Š” ์›์ธ์ด ๋œ๋‹ค.

๐Ÿ‘ CSR ์žฅ์ 
1. ์ž์—ฐ์Šค๋Ÿฌ์šด UX
2. ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋งŒ ๋ถ€๋ถ„์ ์œผ๋กœ ๋กœ๋”ฉ(์„ฑ๋Šฅ)
3. ์„œ๋ฒ„์˜ ํ…œํ”Œ๋ฆฟ ์—ฐ์‚ฐ์„ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ถ„์‚ฐ(์„ฑ๋Šฅ)
4. ์ปดํฌ๋„ŒํŠธ ๋ณ„ ๊ฐœ๋ฐœ ์šฉ์ด(์ƒ์‚ฐ์„ฑ)
5. ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ์„ ์—ผ๋‘์— ๋‘”๋‹ค๋ฉด ๋™์ผํ•œ API๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค๊ณ„ ๊ฐ€๋Šฅ(์ƒ์‚ฐ์„ฑ)

๐Ÿ‘Ž CSR ๋‹จ์ 
1. JavaScript ํŒŒ์ผ์„ ๋ฒˆ๋“ค๋งํ•ด์„œ ํ•œ๋ฒˆ์— ๋ฐ›๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ ๊ตฌ๋™ ์†๋„ ๋Š๋ฆผ(webpack์˜ code splitting์œผ๋กœ ํ•ด๊ฒฐ)
2. ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”(SEO)๊ฐ€ ์–ด๋ ค์›€
3. ๋ณด์•ˆ ์ด์Šˆ(ํ”„๋ก ํŠธ์—”๋“œ์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ตœ์†Œํ™”)

๐ŸŒ SEO (Search Engine Optimzation)
์›น ์‚ฌ์ดํŠธ๊ฐ€ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋” ์ž˜๋ณด์ด๋„๋ก ์ตœ์ ํ™”ํ•˜๋Š” ๊ณผ์ •.
๊ฒ€์ƒ‰ ๋žญํฌ ๊ฐœ์„ ์ด๋ผ๊ณ ๋„ ํ•œ๋‹ค.

 

SSR (Server Side Rendering)

์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง.
MPA(Multiple Page Application)์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
๋ง ๊ทธ๋Œ€๋กœ ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ค„ ํŽ˜์ด์ง€๋ฅผ ๋ชจ๋‘ ๋ Œ๋”๋ง ํ•˜์—ฌ ๋„์šฐ๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

์š”์ฒญ(request) ๋งˆ๋‹ค ์ƒˆ๋กœ๊ณ ์นจ์ด ์ผ์–ด๋‚œ๋‹ค. ์„œ๋ฒ„์— ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ์š”์ฒญ์„ ๊ตฌํ•˜๋Š” ๋ฐฉ์‹์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

View๋ฅผ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ์ฒซ ๋กœ๋”ฉ์ด ๋งค์šฐ ์งง๋‹ค.
View๋ฅผ ์„œ๋ฒ„์—์„œ ์ „๋ถ€ ๋ Œ๋”๋งํ•˜์—ฌ ๋‚ด๋ ค์ค˜์„œ HTML์— ๋ชจ๋“  ์ปจํ…์ธ ๊ฐ€ ์ €์žฅ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— SEO ์ ์šฉ์— ํฐ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค.

๐Ÿ‘ SSR ์žฅ์ 
1. SEO (๊ฒ€์ƒ‰์—”์ง„ ์ตœ์ ํ™”)

๐Ÿ‘Ž SSR ๋‹จ์ 
1. ํŽ˜์ด์ง€ ์ด๋™ ์‹œ ํ™”๋ฉด ๊นœ๋นก์ž„
2. ํŽ˜์ด์ง€ ์ด๋™ ์‹œ ๋ถˆํ•„์š”ํ•œ ํ…œํ”Œ๋ฆฟ๋„ ์ค‘๋ณตํ•ด์„œ ๋กœ๋”ฉ(์„ฑ๋Šฅ)
3. ์„œ๋ฒ„ ๋ Œ๋”๋ง์— ๋”ฐ๋ฅธ ๋ถ€ํ•˜(์„ฑ๋Šฅ)
4. ๋ชจ๋ฐ”์ผ ์•ฑ ๊ฐœ๋ฐœ ์‹œ ์ถ”๊ฐ€์ ์ธ ๋ฐฑ์—”๋“œ ์ž‘์—… ํ•„์š”(์ƒ์‚ฐ์„ฑ)

 

๐Ÿ’ก ์œ„ SSR ๋ฐฉ์‹์€ old server-side rendering ๋ฐฉ์‹์ด๋‹ค.

๊ณผ๊ฑฐ์— ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค reload(๊นœ๋นก์ž„)๊ฐ€ ์ผ์–ด๋‚ฌ๋˜ ์œ„ ๋ฐฉ์‹์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กœ์šด SSR ๊ฐœ๋…์ด ๋“ฑ์žฅํ•˜์˜€๋Š”๋ฐ, ์ด ์ƒˆ SSR์„ ์ ์šฉํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ Universal App์ด๋ผ๊ณ  ํ•œ๋‹ค.

Universal App์˜ SSR ๋™์ž‘ ๋ฐฉ์‹์— ๋Œ€ํ•ด์„  Nuxt.js๋ฅผ ์„ค๋ช…ํ•˜๋ฉฐ ์ด์–ด๋‚˜๊ฐ€๋„๋ก ํ•˜๊ฒ ๋‹ค.

๐ŸŒ„ Nuxt.js๋ž€?

Nuxt.js๋Š” Vue.js ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋ฐ˜์˜ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ๊ตฌ์ถ•์— ๋„์›€์„ ์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.
ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์œ„ํ•œ ํ”„๋ ˆ์ž„ ์›Œํฌ? ๋ฌด์Šจ ๋œป์ธ์ง€ ์ดํ•ด๊ฐ€ ์ž˜ ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.
ํ’€์–ด์„œ ์„ค๋ช…ํ•ด๋ณด์ž๋ฉด...
Vue.js ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์—ฌ๋Ÿฌ ์œ ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ํƒ‘์žฌํ•˜๊ณ  ์žˆ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

 

Nuxt.js ์— ํฌํ•จ๋œ ๊ธฐ๋Šฅ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Vue 2
  • Vue Router
  • Vuex
  • Vue Server Renderer
  • vue-meta
  • vue-loader
  • babel-loader
  • Webpack

๐ŸŒ„ Nuxt.js์˜ ํŠน์ง•

Vue + ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ตฌ์กฐ์ด๊ธฐ ๋•Œ๋ฌธ์—, Nuxt.js๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง„๋‹ค.

  • Vue ํŒŒ์ผ ์‚ฌ์šฉ
  • ์ฝ”๋“œ ๋ถ„ํ•  ์ž๋™ํ™”
  • ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง
  • ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜์˜ ๊ฐ•๋ ฅํ•œ ๋ผ์šฐํŒ… ์‹œ์Šคํ…œ
  • ์ •์  ํŒŒ์ผ ์ „์†ก
  • ES2015+ ์ง€์›
  • JS & CSS ์ฝ”๋“œ ๋ฒˆ๋“ค๋ง ๋ฐ ์••์ถ•
  • <head> ์š”์†Œ ๊ด€๋ฆฌ (<title>, <meta>, ๊ธฐํƒ€)
  • ๊ฐœ๋ฐœ ์ค‘ Hot module ๋Œ€์ฒด
  • ์ „ ์ฒ˜๋ฆฌ๊ธฐ ์ง€์› : SASS, LESS, Stylus ๋“ฑ

๐ŸŒ„ Nuxt.js๋Š” ์–ธ์ œ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€?

๊ฒฐ์ •์ ์œผ๋กœ, SEO ๊ฐœ์„ ์„ ํ• ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.

๐ŸŒ„ Nuxt.js ์„ค์น˜

npx

npx create-nuxt-app <project-name>

yarn

yarn create nuxt-app <project-name>

npm

npm init nuxt-app <project-name>

vue-cli๋ฅผ ํ†ตํ•œ Nuxt.js ์„ค์น˜

npm i -g @vue/cli
npm i -g @vue/cli-init

vue init nuxt-community/starter-template <project-name>
cd <project-name>
npm i

๐Ÿšจ ์ฃผ์˜
vue cli init ๊ธฐ๋Šฅ์€ Vue CLI 2.x์˜ ๊ธฐ๋Šฅ์œผ๋กœ ํ˜„์žฌ ๋ ˆ๊ฑฐ์‹œ(legacy)๋กœ ์ทจ๊ธ‰๋˜๊ณ  ์žˆ๋‹ค.
cli init template ๋ฐฉ์‹์€ ๊ถŒ์žฅํ•˜์ง€ ์•Š์œผ๋‹ˆ ์ฐธ๊ณ .
์ž์„ธํžˆ

Nuxt.js ๋‹จ์ผ ์„ค์น˜

npm i nuxt

๋‚˜๋Š” npm ์œผ๋กœ ์„ค์น˜ํ•ด๋ณด์•˜๋‹ค. ์„ค์น˜์‹œ ์„ ํƒํ–ˆ๋˜ ์˜ต์…˜์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

install Nuxt.js

cd <ํ”„๋กœ์ ํŠธ ๋ช…>
npm run dev

localhost:3000

npm run dev ๋ช…๋ น์–ด ์‹คํ–‰ ํ›„ ์œ„์™€ ๊ฐ™์€ ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚˜๋ฉด Nuxt.js ์„ค์น˜ ๋ฐ ์‹คํ–‰ ์„ฑ๊ณต! ๐ŸŽ‰

๐ŸŒ„ Nuxt.js ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

  • assets
    • css, image, font์™€ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค๋“ค์„ ํฌํ•จํ•œ๋‹ค.
  • components
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋  ์ปดํฌ๋„ŒํŠธ๋“ค์„ ํฌํ•จํ•œ๋‹ค.
    • ํ•ด๋‹น ๊ฒฝ๋กœ์— ์œ„์น˜๋œ ์ปดํฌ๋„ŒํŠธ๋“ค์€ Nuxt.js์˜ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํ•จ์ˆ˜์ธ asyncData ๋˜๋Š” fetch๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.
  • layouts
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์ฒด์— ๋Œ€ํ•œ ๋ ˆ์ด์•„์›ƒ์„ ํฌํ•จํ•œ๋‹ค.
    • ๊ธฐ๋ณธ์œผ๋กœ default.vue ๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์žˆ๋‹ค.
    • ๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€
  • middleware
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋  middleware๋ฅผ ํฌํ•จํ•œ๋‹ค.
    • middleware๋Š” ํŽ˜์ด์ง€ ๋˜๋Š” ๋ ˆ์ด์•„์›ƒ์ด ๋ Œ๋”๋ง๋˜๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค.
    • middleware๋ฅผ ํŽ˜์ด์ง€๋‚˜ ๋ ˆ์ด์•„์›ƒ์— ๋ฐ”์ธ๋”ฉํ•˜์˜€๋‹ค๋ฉด ํ•ด๋‹น ํŽ˜์ด์ง€๋‚˜ ๋ ˆ์ด์•„์›ƒ์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— ๋งค๋ฒˆ ์‹คํ–‰๋œ๋‹ค.
  • node_modules
    • Nuxtํ”„๋ ˆ์ž„์›Œํฌ์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ํ™•์žฅ, ํ†ตํ•ฉ, ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ชจ๋“ˆ์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • pages
    • ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํŽ˜์ด์ง€ ๊ตฌ์„ฑ์„ ํฌํ•จํ•œ๋‹ค.
    • ์ด ๋””๋ ‰ํ† ๋ฆฌ์˜ ๊ตฌ์กฐ์— ๋”ฐ๋ผ router๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋œ๋‹ค.
    • ๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€
  • plugins
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋ฐ”์ธ๋”ฉ๋  ์™ธ๋ถ€ ํ˜น์€ ๋‚ด๋ถ€ plugins๋ฅผ ํฌํ•จํ•œ๋‹ค.
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ธ์Šคํ„ด์Šคํ™” ๋˜๊ธฐ ์ „์— ์‹คํ–‰ํ•˜๋ฉฐ ์ „์—ญ์ ์œผ๋กœ ๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋“ฑ๋กํ•˜๊ณ  ํ•จ์ˆ˜ ๋˜๋Š” ์ƒ์ˆ˜๋ฅผ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • static
    • ์ •์  ํŒŒ์ผ ํฌํ•จ
    • ๊ตฌ์„ฑ์— ๋”ฐ๋ผ html, js ํŒŒ์ผ๋„ ํฌํ•จ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
    • ๋””๋ ‰ํ† ๋ฆฌ ์ด๋ฆ„ ๋ณ€๊ฒฝ ๋ถˆ๊ฐ€
  • store
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋  vuex store ํŒŒ์ผ๋“ค์„ ํฌํ•จํ•œ๋‹ค.
    • ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„ํ™œ์„ฑํ™” ์ƒํƒœ
    • store ๋””๋ ‰ํ† ๋ฆฌ์— index.js ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜๋ฉด store๊ฐ€ ํ™œ์„ฑํ™” ๋œ๋‹ค.
    • ๊ตฌ์„ฑ์— ๋”ฐ๋ผ ๋ชจ๋“ˆ ํ˜•ํƒœ์˜ store๋ฅผ ํ˜•์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
  • content
    • ๐Ÿš€ ์˜ต์…˜
    • @nuxt/content ๋ชจ๋“ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.
    • Markdown, JSON, YAML, XML, CSV ์™€ ๊ฐ™์€ ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ค๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

Vue.js ์™€ Nuxt.js์˜ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ๋น„๊ต

// Vue.js
npm i -g @vue/cli
vue create <ํ”„๋กœ์ ํŠธ ๋ช…>
cd <ํ”„๋กœ์ ํŠธ ๋ช…>
vue add vuex
vue add router

// Nuxt.js
npm init nuxt-app <ํ”„๋กœ์ ํŠธ ๋ช…>

Vue.js, Nuxt.js ๋””๋ ‰ํ† ๋ฆฌ ๋น„๊ต

์œ„ ๋ช…๋ น์–ด๋กœ ์ƒ์„ฑํ•œ ๋‘ ํ”„๋กœ์ ํŠธ๋ฅผ ๋น„๊ตํ•ด๋ณด์•˜๋‹ค.
๋ฒ ์ด์งํ•œ Vue.js ํ”„๋กœ์ ํŠธ์—” Vuex์™€ Vue Router๋ฅผ ์ถ”๊ฐ€ํ•œ ์ƒํƒœ์ด๊ณ , Nuxt.js๋Š” ๋ณ„๋‹ค๋ฅธ ์ถ”๊ฐ€๋ฅผ ํ•˜์ง€ ์•Š์•˜๋‹ค.

 

Vue.js ์—์„œ src ํด๋”์— ์žˆ๋˜ ๋‚ด์šฉ๋“ค์ด Nust.js์—์„  ์ „๋ฐ˜์ ์œผ๋กœ ๋ฃจํŠธ ๋ ˆ๋ฒจ๋กœ ์˜ฌ๋ผ์™€ ์žˆ์—ˆ๋‹ค.

 

Vue.js ์™€ Nuxt.js ๊ฐ€ ๋‘˜๋‹ค ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋””๋ ‰ํ† ๋ฆฌ๋Š” ์„ ์œผ๋กœ ์ด์–ด๋ณด์•˜๋‹ค.

Vue.js ํ”„๋กœ์ ํŠธ์—์„  Router ๊ด€๋ จ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ router, view ์˜€์ง€๋งŒ Nuxt.js ํ”„๋กœ์ ํŠธ์—์„  pages ํด๋” ํ•˜๋‚˜๊ฐ€ ๋Œ€์‹ ํ•œ๋‹ค.
Vue.js ํ”„๋กœ์ ํŠธ์—์„œ Router๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ ค๋ฉด router/index.js์—์„œ ์ง์ ‘ ๋ผ์šฐํ„ฐ๋ฅผ ๋“ฑ๋กํ•ด์คฌ์–ด์•ผ ํ–ˆ๋‹ค.
ํ•˜์ง€๋งŒ Nuxt.js ๋Š” pages ํด๋”์˜ ๊ตฌ์กฐ๋Œ€๋กœ ๋ผ์šฐํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ค€๋‹ค๊ณ ํ•œ๋‹ค ๐Ÿ˜ฎ

Vue.js ์—์„œ ๋ณด์ด์ง€ ์•Š๋˜ middleware, layouts, plugins ๋””๋ ‰ํ† ๋ฆฌ ๋“ฑ๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐ŸŒ„ Nuxt.js ๋ Œ๋”๋ง ๋ชจ๋“œ

https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-mode

Nuxt.js๋Š” Single Page App(SPA), Universal App, Static App์„ ์ง€์›ํ•œ๋‹ค.
์ด๋Š” nuxt.config.js์—์„œ mode ํ”„๋กœํผํ‹ฐ๋ฅผ ํ†ตํ•ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. (mode: 'spa'/'universal')


์œ„ ์Šคํฌ๋ฆฐ์ƒท(๊ณต์‹๋ฌธ์„œ)์„ ๋ณด๋ฉด universal์— ๋Œ€ํ•ด "Isomorphic application (server-side rendeing + client-side navigation)" ๋ผ๊ณ  ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋‹ค.
์ด๊ฑด ๋ฌด์Šจ ์˜๋ฏธ์ผ๊นŒ?

๐Ÿ“ข ์ฐธ๊ณ )
ssr์„ mode๋กœ ์ง€์ •ํ•ด ์ฃผ๋˜ ๋ฐฉ์‹์€ deprecated ๋˜์—ˆ๋‹ค.
build: { ssr: true/false } ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝ๋จ. ์ฐธ๊ณ 

server-side rendering + client-side navigation

์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง(SSR) + ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋„ค๋น„๊ฒŒ์ด์…˜(CSN)์€ ๋ฌด์Šจ ๋ง์ผ๊นŒ?
์ด๋Š” "๐Ÿ“ข ๋“ค์–ด๊ฐ€๊ธฐ ์ „์—" ๋ถ€๋ถ„์—์„œ ์–ธ๊ธ‰ํ–ˆ์—ˆ๋˜ ์ƒˆ๋กœ์šด ๋ฐฉ์‹์˜ SSR, ์ฆ‰ Universal App์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ์˜๋ฏธํ•œ๋‹ค.

๊ณผ๊ฑฐ์— ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ๊นœ๋นก์ž„์ด ์ผ์–ด๋‚ฌ๋˜ ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ์ƒˆ๋กญ๊ฒŒ ๋‚˜ํƒ€๋‚œ ๋ Œ๋”๋ง ๋ฐฉ๋ฒ•์ด๋‹ค.


์ฒซ ํ™”๋ฉด๋งŒ ๊ณผ๊ฑฐ์˜ ์„œ๋ฒ„ ๋ Œ๋”๋ง ์ฒ˜๋Ÿผ ์™„์„ฑ๋œ HTML์„ ๋ฟŒ๋ ค์ฃผ๊ณ (SSR), ์ด ํ›„์—” AJAX๋กœ ๋™์  ๋ผ์šฐํŒ…์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ๋งŒ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ข‹๊ฒ ๋‹ค(CSN)๊ณ  ์ƒ๊ฐํ•˜์—ฌ ๋“ฑ์žฅํ•œ ๋ Œ๋”๋ง ๋ฐฉ์‹์ด๋‹ค. 

 

https://www.a-ha.io/questions/4b35011ae851461fbd04f6467782da0c

 

Universal App์˜ ๋™์ž‘ ๊ณผ์ •์€ ์œ„ ๊ทธ๋ฆผ๊ณผ ๊ฐ™๋‹ค.

 

์ฒ˜์Œ ๋ฆฌํ€˜์ŠคํŠธ๊ฐ€ ๋„์ฐฉํ•˜๋ฉด ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ nuxtServeInit, middleware validate(), asyncData(), fetch() ๋“ฑ์˜ ๊ณผ์ •์„ ๊ฑฐ์ณ์„œ ๋ Œ๋”๋งํ•œ ํŽ˜์ด์ง€๋ฅผ responseํ•œ๋‹ค. (๊ฐ ๊ณผ์ •์€ ์•„๋ž˜์—์„œ ๊ตฌ์ฒด์ ์œผ๋กœ ๋‹ค๋ฃฌ๋‹ค.)

๐Ÿ‘‰ ์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ SSR(Server-side Rendering) ๋ถ€๋ถ„์ด๋‹ค. ์„œ๋ฒ„์—์„œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋ง!

๐Ÿ‘‰ ์ด ํ›„, nuxt-link ํƒœ๊ทธ๋กœ Navigate๊ฐ€ ์ด๋ฃจ์–ด์ง€๋Š”๋ฐ, ์ด๋ฅผ CSN(Client-side Navigation) ์ด๋ผ๊ณ  ํ•œ๋‹ค. 

 

CSN์— ๋Œ€ํ•ด์„œ ๊ตฌ์ฒด์ ์œผ๋กœ ์•Œ์•„๋ณด์ž.

CSN์€ ํฌ๊ฒŒ ํ”„๋ฆฌํŽ˜์น˜ + ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๊ณผ์ •์œผ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๋‹ค.

 

Nuxt.js๋Š” Universal ๋ชจ๋“œ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์š”์ฒญํ•œ URL์„ ํ†ตํ•ด ๋กœ๋“œํ•ด์•ผํ•  ํŽ˜์ด์ง€๋งŒ!(์ „๋ถ€ X) ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋งํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋„˜๊ฒจ์ค€๋‹ค. 

์š”์ฒญ์„ ๋ฐ›์„ ๋•Œ ๋งˆ๋‹ค ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง์„ ํ•œ๋‹ค๋ฉด ๋‹น์—ฐํžˆ ๊นœ๋นก์ž„์ด ๋ฐœ์ƒํ•  ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

ํ•˜์ง€๋งŒ Nuxt.js์˜ Universal ๋ชจ๋“œ์—์„  ๊นœ๋นก์ž„์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋ฐ, ๊ทธ ์ด์œ ๋Š” ๋ฐ”๋กœ ํ”„๋ฆฌ ํŽ˜์น˜(Pre-fetch) ๋•Œ๋ฌธ์ด๋‹ค.

ํ”„๋ฆฌํŽ˜์น˜๋Š” CSN(client side navigation) ์— ํ•ด๋‹นํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.
์ฆ‰, Universal App์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ Vue CLI ์™€ ๊ฐ™์€ SPA์—์„œ๋„ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์ž„์„ ์•Œ์•„๋‘์ž.

ํ”„๋ฆฌ ํŽ˜์น˜

ํ”„๋ฆฌ ํŽ˜์น˜๋Š” ๋ฏธ๋ฆฌ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์˜จ๋‹ค ๋ผ๋Š” ๋œป์ด๋‹ค.

 

๋ง ๊ทธ๋Œ€๋กœ, ๋ Œ๋”๋ง ํ•ด์•ผํ•  ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ›์•„์˜จ๋‹ค๋Š” ๋œป.

Nuxt.js ๊ฐ€ ์˜ˆ์–ธ์„ ํ•˜๋Š” ๊ฒƒ๋„ ์•„๋‹ˆ๊ณ ... ์–ด๋–ป๊ฒŒ ๋ฏธ๋ฆฌ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜ ์žˆ์„๊นŒ?

 

์ด๋Š” Nuxt.js์˜ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜์ธ nuxt-link๋ฅผ ํ†ตํ•ด์„œ ๊ฐ€๋Šฅํ•˜๋‹ค. (Vue router์˜ router-link ์™€ ๊ฐ™๋‹ค๊ณ  ๋ณด๋ฉด ๋จ)

 

Nuxt.js๋Š” ๊ฐ€์žฅ ์ฒ˜์Œ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ HTML์„ ๋ Œ๋”๋ง ํ•ด ์˜ค๊ณ ,

๊ทธ ์ดํ›„ viewport (ํ™”๋ฉด์— ๋ณด์—ฌ์ง€๋Š” ํŽ˜์ด์ง€) ์˜ nuxt-link ๋กœ๋ถ€ํ„ฐ ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ์˜ˆ์ธก ํ•ด

๋ฐฑ ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฒญํฌ ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œ ํ•ด์˜จ๋‹ค.

 

์ด ๋•Œ, ๋ฏธ๋ฆฌ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ๋ฐ์ดํ„ฐ(์ฒญํฌ ํŒŒ์ผ)์˜ ํ˜•์‹์€ js์ด๋‹ค.
Nuxt.js๋Š” ์ž๋™ code splitting(ํŒŒ์ผ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ๋‚œ๋„์งˆ ํ•˜๋Š” ๊ฒƒ)์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์—,
์ƒˆ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋ง ํ•˜๊ณ  ์‹ถ์„ ๋• ์„œ๋ฒ„์— ๋งค๋ฒˆ ๋ Œ๋”๋ง ํ•œ HTML์„ ์š”์ฒญํ•˜๋Š” ๋Œ€์‹ ,
๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋•๋Š” js ํŒŒ์ผ์„ ์š”์ฒญํ•œ๋‹ค.

 

์š”์•ฝํ•˜์ž๋ฉด, Universal App์ด ๊นœ๋นก์ž„ ์—†์ด ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ•ด ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์ด์œ ๋Š” 

๋‹ค์Œ ํŽ˜์ด์ง€์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ(.js)๋ฅผ ๋ฏธ๋ฆฌ ๋ฐ›์•„์˜ค๊ธฐ(ํ”„๋ฆฌ ํŽ˜์น˜) ๋•Œ๋ฌธ์ด๋‹ค.

ํ•˜์ด๋“œ๋ ˆ์ด์…˜

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

 

โ›ณ ์ •๋ฆฌ

  • Universal App์€ server side rendering ๊ณผ client side navigation ๊ณผ์ •์„ ํ†ตํ•ด ๋™์ž‘ํ•œ๋‹ค.
  • server side rendering์€ ์šฐ๋ฆฌ๊ฐ€ ์•Œ๊ณ  ์žˆ๋Š” ์„œ๋ฒ„์—์„œ HTML์„ ๋ Œ๋”๋ง ํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
  • server side rendering ํ›„, view port์˜ nuxt-link ํƒœ๊ทธ๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋‹ค์šด๋กœ๋“œ ํ•ด ์˜จ๋‹ค. (ํ”„๋ฆฌํŽ˜์น˜)
  • ๋ฏธ๋ฆฌ ๋‹ค์šด๋กœ๋“œ ํ•ด ์™”๊ธฐ ๋•Œ๋ฌธ์— ๊นœ๋นก์ž„ ์—†์ด ํŽ˜์ด์ง€ ์ด๋™์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํŽ˜์ด์ง€ ์ด๋™ ํ›„ ์ด๋ฃจ์–ด์ง€๋Š” ๋™์ž‘๋“ค์€ ํ•˜์ด๋“œ๋ ˆ์ด์…˜์ด๋ผ๊ณ  ํ•œ๋‹ค. 
  • ํ•˜์ด๋“œ๋ ˆ์ด์…˜์œผ๋กœ ์ธํ•ด Universal App์€ SPA ์™€ ๋™์ผํ•œ ๋™์ž‘๊ณผ ๋ฐ˜์‘์„ฑ์„ ๋ณด์žฅ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Isomorphic application

Isomorphic ์€ ์ง์—ญํ•˜๋ฉด "๋™์ผํ•œ ๊ตฌ์กฐ์˜" ๋ผ๋Š” ๋œป์ด๋‹ค.
๋ณดํ†ต Isomorphic JavaScript๋ผ๋Š” ๋ง๋กœ ๋งŽ์ด ์“ฐ์ด๋Š”๋ฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ๋™ํ˜• ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ผ๊ณ  ๋ฒˆ์—ญํ•œ๋‹ค.

์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ์— ๊ฐ™์€ ์–ธ์–ด๊ฐ€ ์“ฐ์ธ๋‹ค๋Š” ์˜๋ฏธ๋กœ ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

nuxtServeInit, middleware validate(), asyncData(), fetch() ๋“ฑ์˜ ๊ณผ์ •์„ ์ตœ์ดˆ ์š”์ฒญ์—์„œ๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ์—์„œ ์ฒ˜๋ฆฌ ํ•˜๋Š” ๋กœ์ง์ด ๊ฐ™์€ JavaScript๋กœ ์ž‘์„ฑ๋˜์—ˆ์Œ์„ ์˜๋ฏธํ•œ๋‹ค.

 

๐Ÿšจ ์—ฌ๊ธฐ์„œ ์–˜๊ธฐํ•˜๋Š” ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์˜ "์„œ๋ฒ„"๋Š” Api๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ์•„๋‹ˆ๋ผ Nuxt.js์— ๋‚ด์žฅ๋œ Express(Node.js) ์„œ๋ฒ„๋ฅผ ๋งํ•œ๋‹ค. Nuxt.js๋Š” SSR ๊ตฌํ˜„์„ ์œ„ํ•ด Express ์„œ๋ฒ„๋ฅผ ๋‚ด์žฅํ•˜๊ณ  ์žˆ๋‹ค.

 

Express(Node.js) ์„œ๋ฒ„์™€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋™์ผํ•˜๊ฒŒ JavaScript๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Isomorphic JavaScript / Universal SSR์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์‹ค์ œ ์ฝ”๋”ฉํ•  ๋•Œ ํ•ด๋‹น ์ฝ”๋“œ๊ฐ€ ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ์–‘์ชฝ์—์„œ ๋ชจ๋‘ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ํ•ญ์ƒ ์—ผ๋‘์— ๋‘๊ณ  ์ž‘์—…ํ•ด์•ผํ•œ๋‹ค.

Static App

Nuxt.js๋Š” Univeral App, SPA ์™ธ์—๋„ Static App์„ ์ง€์›ํ•œ๋‹ค.
Static App์€ ๋ชจ๋“  page๊ฐ€ pre-predering(ํ”„๋ฆฌ๋žœ๋”๋ง)๋œ ๋นŒ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  server๋Š” ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค.
์ฆ‰, ์™„์„ฑ๋œ ์ •์  HTML์„ ์ƒ์„ฑํ•ด์„œ ๋ฟŒ๋ ค์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

๐Ÿ“Œ ํ”„๋ฆฌ ๋ Œ๋”๋ง

์„œ๋ฒ„์˜ ๊ฐœ์ž… ์—†์ด ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง ๋œ ๋ชจ๋“  ํŽ˜์ด์ง€์— ๋Œ€ํ•œ HTML ํŒŒ์ผ๋“ค์„ ํด๋ผ์ด์–ธํŠธ์— ์ œ๊ณตํ•ด์ฃผ๋Š” ๊ฒƒ

 

Static App์„ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด nuxt.config.js์— target: 'static' ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.
๋ฐฐํฌ ์‹œ npm run generate์„ ํ•˜๋ฉด dist (default)์— ๋ชจ๋“  ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋œ ๋นŒ๋“œ๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

 

ํ•˜์ง€๋งŒ id๊ฐ™์€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋„˜๊ฒจ ๋ผ์šฐํŒ…ํ•˜๋Š” ํŽ˜์ด์ง€๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์ •ํ•ด์ง„ ๊ฐ’์ด ์•„๋‹ˆ๊ธฐ์— pre-rendering ๋˜์ง€ ์•Š์•„ url ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€ํ•œ ์ด์Šˆ๊ฐ€ ์žˆ๋‹ค.


์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„  nuxt.config.js์˜ generate property ์˜ต์…˜์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด posts๋ผ๋Š” ๋ฆฌ์ŠคํŠธ ํŽ˜์ด์ง€๊ฐ€ ์žˆ๊ณ  post/[id] ์ด ์ƒ์„ธํŽ˜์ด์ง€๋ผ๋ฉด,

generate: {
    routes: function () {
      return [
        '/posts/id๊ฐ’'
      ]
    }
  },

์ € id ๊ฐ’์„ ๋„ฃ์–ด์„œ generate ํ•ด์ฃผ๋ฉด id ๊ฐ’์˜ ํด๋”์™€ ํ•จ๊ป˜ index.html์ด ๋”ฐ๋กœ ์ƒ์„ฑ๋˜๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
ํ•˜์ง€๋งŒ ๋ชจ๋“  id ๊ฐ’์„ config ํŒŒ์ผ์— ๋„ฃ์–ด์„œ ๊ด€๋ฆฌํ•˜๊ธฐ์—” ๋ฌด๋ฆฌ๊ฐ€ ์žˆ์–ด ์ด๋Š” ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๊ฐ’์„ ๋ฐ›์•„์„œ ์„ค์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.

const axios = require('axios'); 

generate: {
    routes: function () {
      return axios.get('http://test.com/posts')
        .then(res => {
            const routes = []
            for (const key in res.data) {
               routes.push('/posts/' + key)
            }
            return routes
        })
    }
  },

๐ŸŒ„ Nuxt.js SSR ๋ฐฐํฌ

Nuxt.js, Vue.js build ๊ฒฐ๊ณผ๋ฌผ ๋น„๊ต

์œ„ ๊ทธ๋ฆผ์€ Vue ํ”„๋กœ์ ํŠธ์™€ Nuxt SSR ํ”„๋กœ์ ํŠธ์˜ ๋ฐฐํฌ๋ฌผ์„ ๋น„๊ตํ•ด๋ณธ ๊ฒƒ์ด๋‹ค.

 

์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ์œ„ํ•ด์„  ๋‹น์—ฐํžˆ ์„œ๋ฒ„ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•ด ๋นŒ๋“œ๊ฐ€ ์ด๋ค„์ ธ์•ผํ•œ๋‹ค.
๋•Œ๋ฌธ์— ๋นŒ๋“œ ํ›„ Nuxt.js ํ”„๋กœ์ ํŠธ์˜.nuxt/dist๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด client,server ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋‚˜๋ˆ ์ง„๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.


๋ฐ˜๋ฉด Vue.js์˜ dist์—์„  ํด๋ผ์ด์–ธํŠธ ํŽ˜์ด์ง€ ๊ตฌ์„ฑ์— ํ•„์š”ํ•œ js,css ๋””๋ ‰ํ† ๋ฆฌ ๋“ฑ๋งŒ ์กด์žฌํ–ˆ๋‹ค.

Vue.js๋Š” ๋ฐฐํฌ ์‹œ ๋ณดํ†ต ๋ฐฑ์—”๋“œ ์„œ๋ฒ„์˜ resource/staticํด๋”๋ฅผ output ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์„ค์ •ํ•˜์—ฌ ๋นŒ๋“œ๋กœ ๋งŒ๋“ค์–ด์ง„ html, css, js ๋ญ‰์น˜๋“ค ์˜ฌ๋ ค ๋ฐฐํฌํ•œ๋‹ค.

 

ํ•˜์ง€๋งŒ SSR์„ ์œ„ํ•œ Nuxt.js๋Š” ๋ Œ๋”๋ง์„ ์œ„ํ•œ ์„œ๋ฒ„๊ฐ€ ์กด์žฌํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— static์œผ๋กœ ํŒŒ์ผ์„ ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ฐฑ์—”๋“œ์™€ ๋ณ„๊ฐœ์˜ ๋‹ค๋ฅธ ํ˜ธ์ŠคํŒ… ์„œ๋ฒ„๋ฅผ ์ค€๋น„ํ•ด์•ผํ•œ๋‹ค. 

 

Nuxt.js๋„ SPA ๋ชจ๋“œ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— Single Page App์ด๋ผ๋ฉด ๊ผญ ์„œ๋ฒ„๊ฐ€ ๋‘๊ฐœ ์žˆ์„ ํ•„์š”๋Š” ์—†๋‹ค.

๐ŸŒ„ Nuxt.js SPA / Static App ๋ฐฐํฌ

nuxt generate ๋˜๋Š” nuxt build --spa๋กœ SPA ํ˜น์€ ์ •์  ๊ฒฐ๊ณผ๋ฌผ์„ ์ƒ์„ฑํ•ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ nuxt generate ์™€ nuxt build --spa์˜ ์ฐจ์ด์ ์€
nuxt generate๋Š” ํ”„๋ฆฌ๋ Œ๋”๋ง(prerendering)์ด ๋œ SPA์ด๊ณ ,
nuxt build --spa๋Š” ํ”„๋ฆฌ๋ Œ๋”๋ง ๋˜์ง€ ์•Š์€ SPA๋ผ๋Š” ๊ฒƒ์ด๋‹ค.

 

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด SPA์™€ Static App ์ด๋ผ๋Š” ๊ฒƒ์ด๋‹ค.

 

์š”์•ฝ

  Universal App SPA Static App
mode ๊ฐ’ universal spa universal
๋™์ž‘ ๋ฐฉ์‹ ์ตœ์ดˆ view๋Š” ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง ๋˜์–ด ๋กœ๋“œ. ์ดํ›„์—๋Š” spa๋กœ ๋™์ž‘ ์ตœ์ดˆ view ์ ‘๊ทผ ์‹œ spa๋กœ ๋กœ๋“œ ํ›„ ์ดํ›„์—๋„ spa๋กœ ๋™์ž‘ ์ตœ์ดˆ view๋Š” ํ”„๋ฆฌ๋žœ๋”๋ง ๋œ ํŽ˜์ด์ง€ ๋กœ๋“œ. ์ดํ›„์—๋Š” spa๋กœ ๋™์ž‘
์„œ๋ฒ„ ์œ ๋ฌด ํฌํ•จ(node.js ํ•„์š”) ๋ฏธํฌํ•จ ๋ฏธํฌํ•จ
์„œ๋ฒ„ ์‹œ์ž‘  npm run start - -
๋นŒ๋“œ ์ƒ์„ฑ ๋ฐฉ์‹ npm run build npm run build npm run generate
seo ์ตœ์ ํ™” X ์ตœ์ ํ™”

 

๐Ÿ“ข ์ฐธ๊ณ 

https://developers.google.com/web/updates/2019/02/rendering-on-the-web?hl=ko

๐ŸŒ„ Universal App์ธ์ง€ SPA์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ฒ•

๋ธŒ๋ผ์šฐ์ €์—์„œ ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด SPA์ธ์ง€ Universal App ์ธ์ง€ ํ™•์ธํ•˜๋Š” ๋ฒ•์€ ๊ฐ„๋‹จํ•˜๋‹ค. 

 

๋ธŒ๋ผ์šฐ์ €์—์„œ ์˜ค๋ฅธ์ชฝ ๋งˆ์šฐ์Šค ํด๋ฆญ ํ›„,

ํŽ˜์ด์ง€ ์†Œ์Šค๋ณด๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ณด์˜€๋˜ ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ํ•ด๋‹น ํŽ˜์ด์ง€ ์†Œ์Šค์— ์กด์žฌํ•œ๋‹ค๋ฉด SSR ๋ฐฉ์‹์ด๊ณ ,

๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋ฉด SPA ๋ฐฉ์‹์ด๋‹ค.

๐ŸŒ„ Nuxt.js Routing

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

/src/router/index.js

์œ„ ์ฝ”๋“œ๋Š” Vue.js ์˜ ๋ผ์šฐํ„ฐ ์„ค์ • ํŒŒ์ผ์ด๋‹ค.


Vue.js ์—์„  /src/views ๋””๋ ‰ํ† ๋ฆฌ์— ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด ๋ผ์šฐํ„ฐ ์„ค์ • ํŒŒ์ผ(/src/router/index.js)์— ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ๋ผ์šฐํ„ฐ ์„ค์ •์„ ์ผ์ผ์ด ์ž…๋ ฅํ•ด์ค˜์•ผํ–ˆ๋‹ค.
ํ•˜์ง€๋งŒ Nuxt.js์—์„  ๊ทธ๋Ÿด ํ•„์š”๊ฐ€ ์—†๋‹ค!

 

Nuxt.js Router์— ๋Œ€ํ•œ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด์ž.
๋ผ์šฐํ„ฐ๋Š” ํฌ๊ฒŒ ๋‘๊ฐ€์ง€ ์ข…๋ฅ˜๊ฐ€ ์žˆ๋‹ค. "ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›๋Š” Dynamic Route"์™€ "ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์ง€ ์•Š๋Š” Basic Route" ์ด๋‹ค.

 

Basic Route

Nuxt.js๋Š” /pages ํด๋”์— ํŒŒ์ผ๋งŒ ์ƒ์„ฑํ•ด์ฃผ๋ฉด ์ž๋™์œผ๋กœ ๋ผ์šฐํŒ…์ด ๋œ๋‹ค.
/pages ์— HelloWorld.vue ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•ด๋ณด๊ฒ ๋‹ค.

<template>
  <div>
    Hello World!!
  </div>
</template>

<script>
  export default {

  }
</script>

<style scoped>

</style>

/pages/HelloWorld.vue

localhost:3000/helloworld

ํŽ˜์ด์ง€ ์ƒ์„ฑ ํ›„ localhost:3000/helloworld์— ์ ‘์†ํ•ด๋ณด๋ฉด HelloWorld.vue ํŽ˜์ด์ง€๊ฐ€ ์ถœ๋ ฅ๋˜๋Š”๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ•œ ์ผ์ผ๊นŒ?

ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•œ ์ฆ‰์‹œ .nuxt/router.js ์—์„  ์ž๋™ ์„ค์ •์ด ์ด๋ค„์ง€๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

import Vue from 'vue'
import Router from 'vue-router'
import { normalizeURL, decode } from 'ufo'
import { interopDefault } from './utils'
import scrollBehavior from './router.scrollBehavior.js'

const _64fe57bb = () => interopDefault(import('..\\pages\\HelloWorld.vue' /* webpackChunkName: "pages/HelloWorld" */))
const _2fdd1532 = () => interopDefault(import('..\\pages\\index.vue' /* webpackChunkName: "pages/index" */))

const emptyFn = () => {}

Vue.use(Router)

export const routerOptions = {
  mode: 'history',
  base: '/',
  linkActiveClass: 'nuxt-link-active',
  linkExactActiveClass: 'nuxt-link-exact-active',
  scrollBehavior,

  routes: [{
    path: "/HelloWorld",
    component: _64fe57bb,
    name: "HelloWorld"
  }, {
    path: "/",
    component: _2fdd1532,
    name: "index"
  }],

  fallback: false
}

export function createRouter (ssrContext, config) {
  const base = (config._app && config._app.basePath) || routerOptions.base
  const router = new Router({ ...routerOptions, base  })

  // TODO: remove in Nuxt 3
  const originalPush = router.push
  router.push = function push (location, onComplete = emptyFn, onAbort) {
    return originalPush.call(this, location, onComplete, onAbort)
  }

  const resolve = router.resolve.bind(router)
  router.resolve = (to, current, append) => {
    if (typeof to === 'string') {
      to = normalizeURL(to)
    }
    return resolve(to, current, append)
  }

  return router
}

/.nuxt/router.js

์ด ํŒŒ์ผ์„ ๋”ฐ๋กœ ๊ฑด๋“ค์ง€๋„ ์•Š์•˜๋Š”๋ฐ, HelloWorld ๋ผ์šฐํ„ฐ๊ฐ€ ์„ค์ •๋˜์—ˆ๋‹ค.

 

Basic Route - ์ค‘์ฒฉ ๋ผ์šฐํŒ…

์ค‘์ฒฉ ๋ผ์šฐํŒ…

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

/pages ๊ตฌ์กฐ

<template>
  <div>
    <div class="container">
      Container ์ปดํฌ๋„ŒํŠธ ์ž…๋‹ˆ๋‹ค.
      <nuxt-child />
    </div>
  </div>
</template>

<script>
  export default {

  }
</script>

<style scoped>
  .container {
    width: 300px;
    height: 300px;
    background-color: pink;
    z-index: 0;
  }
</style>

/pages/Container.vue

<template>
  <div>
    <div class="content1">
      Content1 ์ปดํฌ๋„ŒํŠธ ์ž…๋‹ˆ๋‹ค.
    </div>
  </div>
</template>

<script>
  export default {

  }
</script>

<style scoped>
  .content1 {
    background-color: lightblue;
    margin: 30px;
    width: 200px;
    height: 200px;
  }
</style>

/pages/Container/Content1.vue

<template>
  <div>
    <div class="content2">
      Content2 ์ปดํฌ๋„ŒํŠธ ์ž…๋‹ˆ๋‹ค.
    </div>
  </div>
</template>

<script>
  export default {

  }
</script>

<style scoped>
  .content2 {
    background-color: lightgoldenrodyellow;
    margin: 30px;
    width: 200px;
    height: 200px;
  }
</style>

/pages/Container/Content2.vue

localhost:3000/container
localhost:3000/container/content1
localhost:3000/container/content1

์ค‘์ฒฉ ๋ผ์šฐํŒ… ์—ญ์‹œ ์ž๋™์œผ๋กœ ์„ค์ • ๋๋‹ค.
์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์™€ ์ด๋ฆ„์ด ๊ฐ™์€ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•œ ๋’ค
ํ•ด๋‹น ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ–ˆ๋‹ค.
์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ๋‚ด์— ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋“ค์–ด๊ฐˆ ์ž๋ฆฌ์— <nuxt-child /> ์ •์˜ํ•ด์ฃผ๋ฉด ๋.
Nuxt.js๊ฐ€ ์ž๋™์œผ๋กœ ๋ผ์šฐํŒ…์„ ์™„๋ฃŒํ•œ๋‹ค.

// ...
routes: [{
    path: "/Container",
    component: _e6f8da14,
    name: "Container",
    children: [{
      path: "Content1",
      component: _4d5cf824,
      name: "Container-Content1"
    }, {
      path: "Content2",
      component: _4d40c922,
      name: "Container-Content2"
    }]
  }, {
    path: "/HelloWorld",
    component: _64fe57bb,
    name: "HelloWorld"
  }, {
    path: "/",
    component: _2fdd1532,
    name: "index"
  }]
// ...

/.nuxt/router.js

๋ผ์šฐํŒ… ์„ค์ •ํŒŒ์ผ์„ ๋ณด๋ฉด ์ƒ์œ„ ๋ผ์šฐํ„ฐ Container์— children ์†์„ฑ์ด ์ถ”๊ฐ€๋˜๊ณ  ํ•˜์œ„ ๋ผ์šฐํ„ฐ๋“ค์ด ์ •์˜๋œ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Dynamic Route

์–ธ๋”๋ฐ” + ํŒŒ์ผ์ด๋ฆ„ ํ˜•ํƒœ๋กœ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๋ฉด ํ•ด๋‹น ํŒŒ์ผ ์ด๋ฆ„์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค.

dynamic route directory

_product_id.vue ๋ผ๋Š” ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์ž…๋ ฅํ•ด์คฌ๋‹ค.

<template>
  <div>
    <div>Editing Product {{ $route.params.product_id }}</div>
  </div>
</template>

<script>
export default {
}
</script>

<style scoped>

</style>

product_id๋ผ๋Š” route์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›์•„์™€ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ฃผ๋Š” ์ฝ”๋“œ์ด๋‹ค.
์ž๋™ ์ปดํŒŒ์ผ ํ›„ .nuxt/router.js๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์žˆ์„ ๊ฒƒ์ด๋‹ค.

{
    path: "/products/edit/:product_id?",
    component: _1cad02a9,
    name: "products-edit-product_id"
  }

product_id๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„์˜จ๋‹ค๋Š” ์„ค์ •์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.

localhost:3000/products/edit/1 ๊ฒฐ๊ณผํ™”๋ฉด

localhost:3000/products/edit/1 ๋กœ ์ ‘๊ทผํ•˜๋ฉด, ๊ฒฝ๋กœ์˜ params๋ฅผ ๋ฐ›์•„ ํ™”๋ฉด์— ๋ฟŒ๋ ค์ฃผ๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

validate({ params, query, store }) {
  return true // if the params are valid
  return false // will stop Nuxt.js to render the route and display the error page
}

Nuxt.js์—์„œ ์ œ๊ณตํ•˜๋Š” validate() ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•˜๋ฉด ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
validate()๋Š” ์ƒˆ ๋ผ์šฐํ„ฐ๋กœ ๋„ค๋น„๊ฒŒ์ดํŒ…(navigating)๋˜๊ธฐ ์ „์— call ๋œ๋‹ค.
Nuxt context ๊ฐ์ฒด๋ฅผ argument๋กœ ๊ฐ–๋Š”๋‹ค. ์ž์„ธํžˆ

<template>
  <div>
    <div>Editing Product {{ $route.params.product_id }}</div>
  </div>
</template>

<script>
export default {
  validate({ params }) {
    // must be a number
    return /^\d+$/.test(params.product_id)
  }
}
</script>

<style scoped>

</style>

http://localhost:3000/products/edit/test ๊ฒฐ๊ณผํ™”๋ฉด

product_id๋ฅผ ์ˆซ์ž๋งŒ ๋ฐ›๋„๋ก ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ณ , http://localhost:3000/products/edit/test๋กœ ์ ‘๊ทผํ•˜๋ฉด
์œ„์™€ ๊ฐ™์ด 404์—๋Ÿฌ๋ฅผ ๋ฑ‰๋Š”๋‹ค.

๐ŸŒ„ Nuxt.js Store

Nuxt๋Š” pages ๋””๋ ‰ํ† ๋ฆฌ์™€ ์œ ์‚ฌํ•œ ๊ตฌ์กฐ๋กœ store๋ฅผ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ค.
Store ๋Š” ํฌ๊ฒŒ Classic ๊ณผ Module ๋ชจ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
(Store ๊ธฐ๋Šฅ์ด ํ•„์š” ์—†๋‹ค๋ฉด store ํด๋”๋ฅผ ์ง€์šฐ๋ฉด ๋จ)

Classic

ํด๋ž˜์‹ ๋ชจ๋“œ๋Š” store ๋””๋ ‰ํ† ๋ฆฌ์— index.js๋ฅผ ํ•„์š”๋กœ ํ•œ๋‹ค. (Vuex ์„ค์ • ํŒŒ์ผ)
์ด index.js์—” Vuex ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” export ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์ผ๋ฐ˜ Vue ํ”„๋กœ์ ํŠธ์—์„œ Vuex๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์›ํ•˜๋Š”๋Œ€๋กœ ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

import Vuex from 'vuex'

const createStore = () => {
 return new Vuex.Store({
   state: ...,
   mutations: ...,
   actions: ...
 })
}

export default createStore

Module

๋ชจ๋“ˆ ๋ชจ๋“œ๋Š” ๋˜ํ•œ store ๋””๋ ‰ํ† ๋ฆฌ์— index.js ๋”ฐ์œ„๋ฅผ ํ•„์š”๋กœ ํ•œ๋‹ค. (๊ผญ index.js ์ผ ํ•„์š” ์—†์Œ. ์›ํ•˜๋Š” ๋ชจ๋“ˆ์˜ ๋„ค์ž„ ์ŠคํŽ˜์ด์Šค๋กœ ์ง€์ •)
๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋“ˆ ๋ชจ๋“œ์—์„  ์ด ํŒŒ์ผ์—์„œ ๋ฃจํŠธ state/mutations/actions ๋งŒ export ์‹œํ‚ค๋ฉด๋œ๋‹ค.

export const state = () => ({})

์˜ˆ๋ฅผ ๋“ค์–ด, store ๋””๋ ‰ํ† ๋ฆฌ ๋‚ด์— product.js ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜๋ฉด,
product๋ผ๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๊ฐ€ ์ƒ์„ฑ๋˜์–ด ๋ชจ๋“ˆํ™”๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

export const state = () => ({
 _id: 0,
 title: 'Unknown',
 price: 0
})

export const actions = {
 load ({ commit }) {
   setTimeout(
     commit,
     1000,
     'update',
     { _id: 1, title: 'Product', price: 99.99 }
   )
 }
}

export const mutations = {
 update (state, product) {
   Object.assign(state, product)
 }
}

/store/product.js

<template>
 <div>
   <h1>View Product {{ product._id }}</h1>
   <p>{{ product.title }}</p>
   <p>Price: {{ product.price }}</p>
 </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
 created () {
   this.$store.dispatch('product/load')
 },
 computed: {
   ...mapState(['product'])
 }
}
</script>

/pages/product/view.vue

product ๋ชจ๋“ˆ์˜ id, title, price๋ฅผ ํ™”๋ฉด์— ๋ฟŒ๋ฆฌ๋Š” ์ฝ”๋“œ์ด๋‹ค.
์•ฝ 1์ดˆ ๋’ค์— load ์•ก์…˜์„ ๊ฑฐ์น˜๋ฉฐ state๊ฐ€ ๊ฐฑ์‹ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

localhost:3000/product/view

๐Ÿšจ ์ฃผ์˜
์œ„ ์˜ˆ์ œ์—์„œ load๋ผ๋Š” ๊ฐ€์งœ API๋ฅผ ๊ตฌํ˜„ํ•ด ์‚ฌ์šฉํ–ˆ๋‹ค.
์—ฌ๊ธฐ์„œ ๋ฌธ์ œ์ ์€, 1์ดˆ๋’ค state๊ฐ€ ๊ฐฑ์‹ ๋˜๊ธฐ ์ „๊นŒ์ง€ 0, Unknown ๋”ฐ์œ„์˜ ์ดˆ๊ธฐํ™” ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด์ธ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.
์•„๋งˆ ์‹ค API์—์„œ๋„ response๊ฐ€ ์™„๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ์ดˆ๊ธฐ๊ฐ’์ด ๋ณด์ด๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.
์šฐ๋ฆฐ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Nuxt์—์„œ ์ œ๊ณตํ•˜๋Š” fetch๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์ž์„ธํ•œ๊ฑด ์•„๋ž˜์—์„œ ์„ค๋ช…!

๐ŸŒ„ Nuxt.js Layouts

Nuxt.js๋Š” navbar, footer, header ๋”ฐ์œ„์˜ ๋ ˆ์ด์•„์›ƒ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
์ผ๋ฐ˜์ ์ธ Vue ํ”„๋กœ์ ํŠธ์˜ App.vue ์™€ ๋น„์Šทํ•œ ๊ธฐ๋Šฅ์ด๋‹ค.

<template>
  <div>
    <h1>Admin Layout</h1>
    <nuxt />
  </div>
</template>

<script>
export default {

}
</script>

<style scoped>

</style>

/layouts/admin-layout.vue

<template>
  <div>
    admin page
  </div>
</template>

<script>
export default {
  layout: 'admin-layout'
}
</script>

<style scoped>

</style>

/pages/admin.vue

layouts ๋””๋ ‰ํ† ๋ฆฌ์— admin-layout.vue๋ผ๋Š” ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ ,
pages ๋””๋ ‰ํ† ๋ฆฌ์— admin.vue๋ผ๋Š” ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์—ˆ๋‹ค.
admin.vue์— layout: 'admin-layout' ์ด๋ผ๊ณ  ์ •์˜ํ•ด์ฃผ๋Š” ๊ฒƒ ๋งŒ์œผ๋กœ ํ•ด๋‹น ํŒŒ์ผ์ด admin-layout.vue์˜ <nuxt /> ์š”์†Œ์— ์œ„์น˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

localhost:3000/admin

์ค‘์š”ํ•œ ๊ฒƒ์€, ๋ ˆ์ด์•„์›ƒ ํŒŒ์ผ์— ๋ฐ˜๋“œ์‹œ <nuxt /> ์š”์†Œ๊ฐ€ ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

๐ŸŒ„ Nuxt.js Middleware

middleware๋Š” pages ๋˜๋Š” layouts๋ฅผ ๋ Œ๋”๋งํ•˜๊ธฐ ์ „์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.
๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•ด๊ฒฐ๋˜๊ธฐ ์ „๊นŒ์ง€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•„๋ฌด๊ฒƒ๋„ ํ‘œ์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค.
์ด๋Š” Vuex ์ €์žฅ์†Œ์—์„œ ์œ ํšจํ•œ ๋กœ๊ทธ์ธ์„ ํ™•์ธํ•˜๊ฑฐ๋‚˜, ์ผ๋ถ€ ๋งค๊ฐœ ๋ณ€์ˆ˜์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. (validate() ๋ฉ”์†Œ๋“œ ๋Œ€์‹ )
์˜ˆ์ œ - CodeSandBox

๐ŸŒ„ Nuxt.js Plugins

plugins ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ƒ์„ฑ๋˜๊ธฐ ์ „์— Vue ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด Vue ์ธ์Šคํ„ด์Šค์˜ ์•ฑ ์ „์ฒด์—์„œ ๊ณต์œ ํ•˜๊ณ  ๋ชจ๋“  ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด vue-notifications ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์ฃผ์ž…ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

  1. npm i vue-notifications (์—ฌ๊ธฐ๊นŒ์ง„ Vue์™€ ๋™์ผ)
  2. plugins ๋””๋ ‰ํ† ๋ฆฌ์— vue-notifications.js ํŒŒ์ผ์„ ํŒŒ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ์ž…๋ ฅ.
  3. import Vue from 'vue' import VueNotifications from 'vue-notifications' Vue.use(VueNotifications)
  4. nuxt.config.js ์— plugins: ['~/plugins/vue-notifications'] ์ž…๋ ฅ

๋์ด๋‹ค. Vue ํ”„๋กœ์ ํŠธ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ์ฃผ์ž…๋ฒ•๊ณผ ์œ ์‚ฌํ•˜๋‹ค.

Nuxt.js ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ

Nuxt.js๋Š” ์ปดํฌ๋„ŒํŠธ์˜ mounted ํ›„ํฌ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด,
ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ๋“œํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์กด Vue ํŒจํ„ด์„ ์ง€์›ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ universal ์•ฑ์„ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค๋ฉด, ์„œ๋ฒ„ ์ธก ๋ Œ๋”๋ง ์ค‘์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋„๋ก Nuxt.js ๊ด€๋ จ ํ›„ํฌ(fetch, asyncData)๋ฅผ ์จ์•ผํ•œ๋‹ค.

asyncData

  • ์ปดํฌ๋„ŒํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์„ธํŒ…ํ•˜๊ธฐ ์ „์— ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋กœ๋“œํ•˜๊ธฐ ์ „์— ํ˜ธ์ถœ๋œ๋‹ค.
  • pages ์ปดํฌ๋„Œใ…ŒํŠธ์—์„œ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Context ๊ฐ์ฒด๋ฅผ ์ฒซ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ๋ฐ›์œผ๋ฉฐ, ์ด๋ฅผ ์‚ฌ์šฉํ•ด ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์ปดํฌ๋„ŒํŠธ ๋ฐ์ดํ„ฐ๋กœ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ฐ˜ํ™˜ ๊ฐ’์€ ์ปดํฌ๋„ŒํŠธ์˜ data์™€ ๋ณ‘ํ•ฉ๋œ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ this๋ฅผ ํ†ตํ•ด ์ปดํฌ๋„ŒํŠธ ์ธ์Šคํ„ด์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋‹ค.
export default {
    async asyncData({ params }) {
        const { data } = await axios.get(`https://my-api/posts/${params.id}`);
        return { title: data.title };
    },
};

fetch

  • ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง ๋˜๊ธฐ ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ Store์— ๋„ฃ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋œ๋‹ค.
  • ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋กœ๋“œํ•˜๊ธฐ ์ „์— ํ˜ธ์ถœ๋œ๋‹ค.
  • Context ๊ฐ์ฒด๋ฅผ ์ฒซ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ๋ฐ›์œผ๋ฉฐ ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์Šคํ† ์–ด์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค.
  • return ๊ฐ’์€ Promise์ด๋‹ค.
  • Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด Nuxt๋Š” ๋ Œ๋”๋ง ์ „์— Promise๊ฐ€ ๋๋‚ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

์œ„ ์ชฝ Store Module ์˜ˆ์ œ์—์„œ ์ด์–ด ์„ค๋ช…ํ•˜๊ฒ ๋‹ค.
<์ฃผ์˜> ๋ถ€๋ถ„์—์„œ ์„ค๋ช…ํ–ˆ์ง€๋งŒ, ์–ด๋–ค Api๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ, response๊ฐ€ ์˜ค๊ธฐ ์ „๊นŒ์ง€
์ดˆ๊ธฐ๊ฐ’์ด ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋œ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ–ˆ์—ˆ๋‹ค.
์ด ๋ฌธ์ œ๋Š” ์œ„ fetchd์˜ ํŠน์ง• ์ค‘ 6๋ฒˆ "Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด Nuxt๋Š” ๋ Œ๋”๋ง ์ „์— Promise๊ฐ€ ๋๋‚ ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฐ๋‹ค."๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 

/store/product.js์˜ load ์•ก์…˜ ๋ฉ”์†Œ๋“œ๊ฐ€ Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ•˜๊ณ , (async/await ๋ฅผ ์จ๋„ ์ƒ๊ด€ ์—†์Œ)
/pages/products/view.vue ํŒŒ์ผ์„ fetch๋กœ ์ˆ˜์ •ํ•ด๋ณด์•˜๋‹ค.

export const state = () => ({
  _id: 0,
  title: 'Unknown',
  price: 0
})

export const actions = {
  load ({ commit }) {
    return new Promise(resolve => {
      setTimeout(() => {
        commit('update', { _id: 1, title: 'Product', price: 99.99 })
        resolve()
      }, 1000)
    })
  }
}

export const mutations = {
  update (state, product) {
    Object.assign(state, product)
  }
}

/store/product.js

<template>
  <div>
    <h1>View Product {{ product._id }}</h1>
    <p>{{ product.title }}</p>
    <p>Price: {{ product.price }}</p>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  fetch() {
    this.$store.dispatch('product/load')
  },
  computed: {
    ...mapState(['product'])
  }
}
</script>

<style scoped>

</style>

/pages/product/view.vue

localhost:3000/product/view ๊ฒฐ๊ณผ ํ™”๋ฉด

์šฐ๋ฆฐ product/load ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „๊นŒ์ง€ ๋ Œ๋”๋ง์ด ๋˜์ง€ ์•Š๋‹ค๊ฐ€, response๊ฐ€ ์˜ค๋ฉด ํ™”๋ฉด์— product/load์˜ ๊ฒฐ๊ณผ ๊ฐ’์ด ๋œฐ ๊ฒƒ์ด๋ผ๊ณ  ์˜ˆ์ƒํ–ˆ๋‹ค.


ํ•˜์ง€๋งŒ ๊ฒฐ๊ณผ์ ์œผ๋กœ, product/load ์•ก์…˜ ๋ฉ”์†Œ๋“œ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š์•˜๋‹ค. ์™œ์ผ๊นŒ?

์ด์œ ๋Š” fetch, asyncData ๊ฐ™์€ Supercharged ๋ฉ”์†Œ๋“œ๋Š” Vue ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๋ฏ€๋กœ ์ปดํฌ๋„ŒํŠธ this ๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€ ์•Š๋Š”๋‹ค. ๋•Œ๋ฌธ์— ์œ„ ์˜ˆ์ œ์˜ this.$store๋Š” undefined ์ƒํƒœ ์ธ๊ฒƒ์ด๋‹ค. ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋œ ์ดํ›„์—” this ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
(fetch์™€ asyncData์˜ ์‹คํ–‰ ํƒ€์ด๋ฐ์€ ์•„๋ž˜ lifeCycle ๋ถ€๋ถ„์—์„œ ํ•œ๋ฒˆ ๋” ๋‹ค๋ฃจ๊ฒ ๋‹ค.)

 

๊ทธ๋ ‡๋‹ค๋ฉด fetch๋‚˜ asyncData๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์–ด๋–ป๊ฒŒ Store์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์„๊นŒ?
๋ฐ”๋กœ Context ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

๐ŸŒ„ Nuxt.js Context

Nuxt๋Š” ๋ชจ๋“  ๋ฉ”์†Œ๋“œ์— Context๋ผ๋Š” ๋งค์šฐ ์œ ์šฉํ•œ ๊ฐ์ฒด๋ฅผ ํฌํ•จํ•˜๋Š” ์ธ์ˆ˜๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
์—ฌ๊ธฐ์—” ์•ฑ ์ „์ฒด์—์„œ ์ฐธ์กฐํ•ด์•ผํ•˜๋Š” ๋ชจ๋“  ๊ฒƒ์ด ์žˆ๋‹ค. ์ฆ‰, Vue๊ฐ€ ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ๋จผ์ € ์ƒ์„ฑํ•  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ํ•„์š”๊ฐ€ ์—†๋‹ค.
(Context๊ฐ€ ๋ฌด์—‡์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€๋Š” ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ์กฐํ•˜์ž.)

์œ„ fetch ์˜ˆ์ œ์—์„œ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ์—ฌ๊ธฐ์„œ Store๋ฅผ ์ถ”์ถœํ•ด์˜ค์ž.

export default {
 fetch ({ store }) {
   return store.dispatch('product/load')
 },
 computed: {...}
}

/pages/product/view.vue

localhost:3000/product/view ์‹คํ–‰ ๊ฒฐ๊ณผ

์ดˆ๊ธฐ๊ฐ’์ด ๋œจ์ง€ ์•Š๊ณ , response๊ฐ€ ์˜ค๊ณ ๋‚˜์„œ์•ผ ๋ Œ๋”๋ง์ด ๋˜๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐ŸŒ„ Nuxt.js ๋ฉ”ํƒ€ ํƒœ๊ทธ

Vue.js์—์„  SEO ๋ฅผ ์œ„ํ•œ ๋ฉ”ํƒ€ ํƒœ๊ทธ๋ฅผ ๋‹ฌ๊ธฐ ์œ„ํ•ด vue-meta ๋“ฑ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ด์šฉํ•ด์•ผํ–ˆ์ง€๋งŒ,
Nust.js์—์„  ๋ณ„๋„์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ ์—†์ด ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ์œผ๋กœ vue-meta ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํƒ‘์žฌ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ „์—ญ ์„ค์ •

export default {
  head: {
    title: 'my website title',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      {
        hid: 'description',
        name: 'description',
        content: 'my website description'
      }
    ],
    link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]
  }
}

nuxt.config.js

Nuxt.js ์„ค์ • ํŒŒ์ผ(nuxt.config.js)์— meta ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•˜๋ฉด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ๊ธฐ๋ณธ ํƒœ๊ทธ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.
SEO ๋ชฉ์ ์œผ๋กœ ๊ธฐ๋ณธ ์ œ๋ชฉ ๋ฐ ์„ค๋ช… ํƒœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋ทฐํฌํŠธ๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ํŒŒ๋น„์ฝ˜์„ ์ถ”๊ฐ€ํ•˜๋Š”๋ฐ ๋งค์šฐ ์œ ์šฉํ•˜๋‹ค.
์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๋ฉด ๋ชจ๋“  ํŽ˜์ด์ง€์— ๋™์ผํ•œ ์ œ๋ชฉ๊ณผ ์„ค๋ช…์ด ํ‘œ์‹œ๋˜๊ฒŒ ๋œ๋‹ค.

 

์ง€์—ญ ์„ค์ •

์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ์˜ <script> ํƒœ๊ทธ ๋‚ด๋ถ€์— ์žˆ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋ณ„ ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

<script>
export default {
  head: {
    title: 'Home page',
    meta: [
      {
        hid: 'description',
        name: 'description',
        content: 'Home page description'
      }
    ],
  }
}
</script>

// ๋ฉ”์†Œ๋“œ๋กœ๋„ ํ‘œํ˜„ ๊ฐ€๋Šฅ
<template>
  <h1>{{ title }}</h1>
</template>
<script>
  export default {
    data() {
      return {
        title: 'Home page'
      }
    },
    head() {
      return {
        title: this.title,
        meta: [
          {
            hid: 'description',
            name: 'description',
            content: 'Home page description'
          }
        ]
      }
    }
  }
</script>

๐ŸŒ„ Nuxt.js LifeCycle

https://ko.nuxtjs.org/docs/2.x/concepts/nuxt-lifecycle/#server

๋ญ”๊ฐ€ ๊ต‰์žฅํžˆ ๋ณต์žกํ•ด๋ณด์ด๋Š” ์ด๋ฏธ์ง€์ด์ง€๋งŒ, ์œ„ ๋‚ด์šฉ์„ ๋ชจ๋‘ ์ฝ๊ณ  ์™”๋‹ค๋ฉด ์ดํ•ดํ•˜๊ธฐ ์ˆ˜์›”ํ•  ๊ฒƒ์ด๋‹ค.

๋จผ์ € ๊ฐ€์šด๋ฐ ๊ทธ์–ด์ง„ ์„ ์„ ์‚ดํŽด๋ณด์ž.
๊ฐ€์šด๋ฐ ์„ ์„ ๊ธฐ์ค€์œผ๋กœ, ์œ„์ชฝ์€ Vue ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „, ์•„๋ž˜๋Š” ๊ทธ ํ›„๊ฐ€ ๋˜๊ฒ ๋‹ค.
์ข€ ๋” ์‰ฝ๊ฒŒ ์–˜๊ธฐํ•˜์ž๋ฉด, ์œ„๋Š” Server Side, ์•„๋ž˜๋Š” Client Side์ด๋‹ค.

 

Vue Component๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „, Server Side์—์„œ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง ๋˜๊ธฐ ์ „ ํ•ด์•ผํ•  ์ผ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
๋‹น์—ฐํ•œ ๋ง์ด์ง€๋งŒ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„  Vue Component๊ฐ€ ์•„์ง ์ƒ์„ฑ๋˜๊ธฐ ์ „์ด๊ธฐ ๋•Œ๋ฌธ์— this๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

์•ž์„œ ์„ค๋ช…ํ–ˆ๋˜ middleware, validate(), asyncData() ๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

 

๐Ÿ’ก ์ฐธ๊ณ 
Nuxt.js 2.12 ์ „์—” fetch()๋ฅผ asyncData() ์™€ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ์ „์— ์‹คํ–‰์„ ํ•ด์คฌ์–ด์•ผํ–ˆ์ง€๋งŒ,
2.12 ์ดํ›„์—” created() ์ดํ›„, ์ฆ‰, Vue ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  ๋‚œ ํ›„์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.
์ •ํ™•ํžˆ๋Š” ๋ธŒ๋ผ์šฐ์ €์— DOM์ด ๋ Œ๋” ๋˜๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค.(beforeMount ์ „)

์ด๋กœ์จ fetch()์—์„œ this ์˜ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ด์ง„ ๊ฒƒ์ด๋‹ค.
์ฆ‰, fetch์˜ ํŠน์ง•์ด์—ˆ๋˜ "๋ฐ์ดํ„ฐ๋ฅผ Store์— ๋„ฃ๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ" ์€ ์„ ํƒ์‚ฌํ•ญ์ด ๋˜์—ˆ๋‹ค.
Vuex ์Šคํ† ์–ด ์ž‘์—…์„ ์ „๋‹ฌํ•˜๊ฑฐ๋‚˜ ํŽ˜์ด์ง€ ๊ตฌ์„ฑ ์š”์†Œ์—์„œ ๋ณ€ํ˜•์„ ์ปค๋ฐ‹ํ•˜์ง€ ์•Š๊ณ ๋„ ๊ตฌ์„ฑ ์š”์†Œ์˜ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋œ ํ›„ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ํ™œ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.

<button @click="$fetch">Refresh</button>
export default {
  methods: {
    refresh() {
      this.$fetch()
    }
  }
}

๐Ÿ‘‰ [Nuxt.js 2.12 fetch() ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ 1]
๐Ÿ‘‰ [Nuxt.js 2.12 fetch() ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ธฐ 2]

 

์•„๋ฌดํŠผ ์š”์•ฝํ•˜์ž๋ฉด Nuxt.js์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ๋Šฅ์ด Vue ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ์ „์— ์ด๋ฃจ์–ด์ง€๊ณ ,

๊ทธ ์ดํ›„๋Š” Vue.js์™€ ๋™์ผํ•˜๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. (fetch ์ œ์™ธ)


์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„  Nuxt.js์— ๋Œ€ํ•ด ์•Œ์•„ ๋ณด์•˜๋‹ค. 

์›น์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ "๋ Œ๋”๋ง" ์ด ์ƒ๊ฐ๋ณด๋‹ค ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค. 

 

ํ”ผ๋“œ๋ฐฑ ๋Œ“๊ธ€์€ ์–ธ์ œ๋‚˜ ํ™˜์˜์ž…๋‹ˆ๋‹ค. ๐Ÿ‘ผ

 

 

๋”๋ณด๊ธฐ

https://maxkim-j.github.io/posts/nuxt-ssr

 

Nuxt๋กœ ์‚ดํŽด๋ณด๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์˜ ํ•ต์‹ฌ

SSR ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํ”„๋ฆฌ๋ Œ๋”๋ง, ํ•˜์ด๋“œ๋ ˆ์ด์…˜์„ ์•Œ์•„๋ด…๋‹ˆ๋‹ค.

maxkim-j.github.io

https://stackoverflow.com/questions/63336570/whats-the-real-difference-between-target-static-and-target-server-in-nuxt

 

What's the real difference between target: 'static' and target: 'server' in Nuxt 2.14 universal mode?

in the latest version of Nuxt (2.14) they introduced an optimization for building the app when no code is changed (for drastically improve build times). I make websites in jamstack, deploy on netlify

stackoverflow.com

https://khwan.kr/blog/vue/2020-03-02-nuxt-spa-ssr/

 

Nuxt๋กœ ์•Œ์•„๋ณด๋Š” SPA, SSR ๊ทธ๋ฆฌ๊ณ  Static Web - khwan

๐Ÿ˜จ ๊ทธ๋™์•ˆ์˜ ์˜คํ•ด ๋‚˜๋Š” ๊ทธ๋™์•ˆ Nuxt๋ฅผ ์ œ๋Œ€๋กœ ์•Œ๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ํ”ํžˆ ๋งํ•˜๋Š” SPA์˜ ํŠน์ง•๊ณผ SSR์˜ ํŠน์ง•์„ ์ดํ•ดํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ๋ฏฟ์—ˆ๊ณ , ์„œ๋น„์Šค์˜ ํŠน์ง•๊ณผ ํ•„์š”์— ๋”ฐ๋ผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค๊ณ  ์•Œ๊ณ  ์žˆ์—ˆ๋‹ค.

khwan.kr

https://www.toptal.com/front-end/client-side-vs-server-side-pre-rendering

 

Client-side vs. Server-side vs. Pre-rendering for Web Apps

User experience tanks when sites feel slow. Today's heavier front ends don't help. In this article, Toptal Freelance Front-end Developer Guillaume Breux compares client-side vs server-side and also pre-rendering strategies to help you choose the best optio

www.toptal.com

 

https://www.toptal.com/vue-js/server-side-rendered-vue-js-using-nuxt-js

 

Creating Server-side Rendered Vue.js Apps Using Nuxt.js

When initially loading your website, your browser doesn't receive a complete page to display. Instead, it gets a bunch of pieces and instructions of how to put them all together. It takes a substantial amount of time to put all this information together be

www.toptal.com

https://seonghui.github.io/TIL/docs/vue/nuxt.html

 

nuxt

์Šคํ…”๋ผ์˜ TIL(Today I Learned)

seonghui.github.io

https://developers.google.com/web/updates/2019/02/rendering-on-the-web?hl=ko 

 

์›น ๋ Œ๋”๋ง  |  Web  |  Google Developers

Jason is a Web DevRel Eng Manager, Web Developer Relations ๊ฐœ๋ฐœ์ž๋กœ์„œ ์šฐ๋ฆฌ๋Š” ์ข…์ข… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ์•„ํ‚คํ…์ฒ˜์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์˜์‚ฌ ๊ฒฐ์ •์„ ํ•˜๊ณค ํ•ฉ๋‹ˆ๋‹ค. ์›น ๊ฐœ๋ฐœ์ž๊ฐ€ ํ•˜๋Š” ๊ฐ€์žฅ ํ•ต์‹ฌ์ ์ธ ๊ฒฐ์ • ์ค‘ ํ•˜๋‚˜๋Š”

developers.google.com

 

Seize the day!

Spring MVC | Spring Boot | Spring Security | Mysql | Oracle | PostgreSQL | Vue.js | Nuxt.js | React.js | TypeScript | JSP | Frontend | Backend | Full Stack | ์ž๊ธฐ๊ณ„๋ฐœ | ๋ฏธ๋ผํด ๋ชจ๋‹ | ์ผ์ƒ