Nuxt์˜ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ๋ฐฉ๋ฒ•

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

์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋‹ค๋ฅธ ์ 

ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์ธ ๋ทฐ ์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์˜ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ๋ฐฉ์‹์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<!-- UserProfile.vue -->
<template>
  <div>
    <p>{{ user }}</p>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      user: {},
    }
  },
  methods: {
    async fetchUser() {
      const response = await axios.get('/users/1');
      this.user = response.data;
    }
  },
  created() {
    this.fetchUser();
  },
}
</script>

created() ๋ผ์ดํ”„ ์‚ฌ์ดํด ํ›…์„ ์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜์ž๋งˆ์ž ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•ด ๋ฐ›์•„์˜จ ๊ฐ’์„ ํ™”๋ฉด์— ํ‘œ์‹œํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ด๋•Œ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” ์‹œ์ ์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ Vue.js ์ฝ”๋“œ๊ฐ€ ํ™”๋ฉด์˜ DOM์„ ๊ตฌ์„ฑํ•˜๊ณ  ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์‹œ์ ์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง๊ณผ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์ฐจ์ด์ ์˜ ๊ทธ๋ฆผ์—์„œ๋„ ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์€ ๋นˆ ํ™”๋ฉด์„ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ฐ›์•„ ํ™”๋ฉด์— ๋ฟŒ๋ฆด ์š”์†Œ์™€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ตฌ์„ฑํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์œ„์™€ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ, ๋„‰์ŠคํŠธ๋Š” ์„œ๋ฒ„์—์„œ ํŽ˜์ด์ง€์˜ ๋‚ด์šฉ์„ ๋ชจ๋‘ ๊ทธ๋ ค์„œ ๋ธŒ๋ผ์šฐ์ €๋กœ ๊ฐ€์ ธ๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ์š”?

๋„‰์ŠคํŠธ์˜ REST API ํ˜ธ์ถœ ๋ฐฉ์‹

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

  • asyncData
  • fetch

asyncData

asyncData๋Š” ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ(pages ํด๋” ์•„๋ž˜์— ์œ„์น˜ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ)์—๋งŒ ์ œ๊ณต๋˜๋Š” ์†์„ฑ์ž…๋‹ˆ๋‹ค. asyncData๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<!-- pages/user.vue -->
<template>
  <div>
    <p>{{ user }}</p>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  // params์˜ id๊ฐ€ 1์ด๋ผ๊ณ  ๊ฐ€์ •
  async asyncData({ params, $http }) {
    const response = await axios.get(`/users/${params.id}`);
    const user = response.data;
    return { user }
  }
}
</script>

์œ„ ์ฝ”๋“œ๋Š” URL /user๋กœ ์ ‘๊ทผํ•  ๋•Œ user.vue ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๊ธฐ ์ „์— ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค ๋ฐ›์•„์™€์•ผ์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋“ค๊ณ  <template></template> ์˜์—ญ์˜ ์ฝ”๋“œ๋ฅผ ํ™”๋ฉด์— ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์น˜ ์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ทฐ ๋ผ์šฐํ„ฐ์—์„œ ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ฐ€๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋ฐ›์•„์™”์„ ๋•Œ ํŽ˜์ด์ง€๋ฅผ ์ง„์ž…ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ผ์šฐํ„ฐ ๋„ค๋น„๊ฒŒ์ด์…˜ ๊ฐ€๋“œ์˜ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ๋ฐฉ๋ฒ•์€ ์•„๋ž˜ ์˜์ƒ์„ ์ฐธ๊ณ ํ•˜์„ธ์š” ๐Ÿ˜ƒ
Vue.js ์™„๋ฒฝ ๊ฐ€์ด๋“œ - ๋ผ์šฐํ„ฐ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ๋ฐฉ๋ฒ•

asyncData์˜ ํŒŒ๋ผ๋ฏธํ„ฐ

asyncData ์†์„ฑ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” context ์†์„ฑ์ž…๋‹ˆ๋‹ค. ์ปจํ…์ŠคํŠธ ์†์„ฑ์€ ๋„‰์ŠคํŠธ ํ”„๋ ˆ์ž„์›Œํฌ ์ „๋ฐ˜์— ๊ฑธ์ณ ๊ณต์šฉ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์†์„ฑ์œผ๋กœ์จ ํ”Œ๋Ÿฌ๊ทธ์ธ, ๋ฏธ๋“ค์›จ์–ด ๋“ฑ์˜ ์†์„ฑ์—์„œ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ปจํ…์ŠคํŠธ์—๋Š” ์Šคํ† ์–ด, ๋ผ์šฐํ„ฐ ๊ด€๋ จ ์ •๋ณด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ์—์„œ ์š”์ฒญ, ์‘๋‹ต ๊ด€๋ จ๋œ ์†์„ฑ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

function (context) { // asyncData, plugins, middleware, ...
  // Always available
  const {
    app,
    store,
    route,
    params,
    query,
    env,
    isDev,
    isHMR,
    redirect,
    error,
   $config
  } = context

  // Only available on the Server-side
  if (process.server) {
    const { req, res, beforeNuxtRender } = context
  }

  // Only available on the Client-side
  if (process.client) {
    const { from, nuxtState } = context
  }
}

asyncData์˜ ์—๋Ÿฌ ํ•ธ๋“ค๋ง

aysncData ์†์„ฑ์—์„œ API ํ˜ธ์ถœ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์—๋Ÿฌ ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export default {
  async asyncData({ params, $http, error }) {
    try {
      const response = await axios.get(`/users/${params.id}`);
      const user = response.data;
      return { user }
    } catch(e) {
      error({ statusCode: 503, message: 'API ์š”์ฒญ์ด ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”' })
    }
  }
}

fetch

fetch๋Š” ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ผ๋ฐ˜ ๋ทฐ ์ปดํฌ๋„ŒํŠธ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ์†์„ฑ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ 2๊ฐ€์ง€ ์ƒํ™ฉ์—์„œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

  • ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•  ๋•Œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  ๋‚˜์„œ ์‹คํ–‰๋จ
  • ๋ธŒ๋ผ์šฐ์ €์—์„œ URL ์ฃผ์†Œ๋ฅผ ๋ณ€๊ฒฝํ•ด์„œ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ๋•Œ

๊ฐ„๋‹จํ•œ ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

<!-- components/UserProfile.vue -->
<template>
  <div>{{ user }}</div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      user: {},
    }
  },
  async fetch() {
    const res = await axios.get('https://jsonplaceholder.typicode.com/users/1');
    this.user = res.data;
  },
}
</script>

๋งŒ์•ฝ ์œ„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋ž˜์™€ ๊ฐ™์ด main.vue ์ปดํฌ๋„ŒํŠธ์— ๋“ฑ๋ก๋˜์–ด ์žˆ๊ณ  URL ์ฃผ์†Œ๊ฐ€ /์—์„œ /main์œผ๋กœ ๋ณ€๊ฒฝ๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๋ถ€์ฐฉ๋˜๊ณ  ๋‚˜์„œ(mounted) fetch ์•ˆ์˜ ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ๋กœ์ง์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

<!-- pages/main.vue -->
<template>
  <div>
    <h1>๋ฉ”์ธ ํŽ˜์ด์ง€</h1>
    <UserProfile></UserProfile>
  </div>
</template>

<script>
import UserProfile from '@/components/UserProfile.vue'

export default {
  components: {
    UserProfile,
  },
}
</script>

fetch-page-navigation

์œ„์˜ ๋™์ž‘์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด ์ตœ์ดˆ์— /๋กœ ์ ‘๊ทผํ•˜๊ณ  /main์œผ๋กœ ์ด๋™ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๋จผ์ € ๋ฟŒ๋ ค์ง€๊ณ  ๋‚˜์„œ fetch ํ˜ธ์ถœ์ด ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, /main์œผ๋กœ ์ด๋™ํ•˜๊ณ  ๋‚˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋™์•ˆ user ์†์„ฑ์˜ ๊ธฐ๋ณธ ๊ฐ’์ธ {}๊ฐ€ ๋จผ์ € ํ™”๋ฉด์— ๋ณด์ด๊ณ  ์ž ์‹œ ํ›„์— ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ํ™”๋ฉด์— ๊ทธ๋ ค์ง€๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋งŒ์•ฝ ์ตœ์ดˆ๋กœ ์›น ์„œ๋น„์Šค๋ฅผ /main์œผ๋กœ ์ ‘๊ทผํ•˜๊ฒŒ ๋˜๋ฉด ์„œ๋ฒ„์—์„œ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•  ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ํ™”๋ฉด์— ๋ฐ์ดํ„ฐ๊ฐ€ ํ˜ธ์ถœ๋œ ์ƒํƒœ๋กœ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

fetch-ssr-rendering

fetch ํŠน์ง•

fetch๋Š” asyncData์™€ ๋‹ค๋ฅด๊ฒŒ ์•„๋ž˜์™€ ๊ฐ™์€ ์†์„ฑ๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • $fetchState : ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ ์ƒํƒœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์†์„ฑ์ด๋ฉฐ ์ธ์Šคํ„ด์Šค๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜ธ์ถœ ์ƒํƒœ์— ๋”ฐ๋ผ pending, error, timestamp๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • $fetch : fetch ๋กœ์ง์„ ๋‹ค์‹œ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  • fetchOnServer : ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ์‹œ์— ์„œ๋ฒ„์—์„œ fetch๋ฅผ ์‹คํ–‰ํ• ์ง€ ๋ง์ง€ ๊ฒฐ์ •ํ•˜๋Š” ์†์„ฑ์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ true ์ž…๋‹ˆ๋‹ค.

์œ„ ์†์„ฑ์„ ์ฝ”๋“œ๋กœ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.




ย 
ย 


ย 














ย 
ย 






ย 
ย 



<template>
  <div>
    <article>
      <p v-if="$fetchState.pending">์‚ฌ์šฉ์ž API ํ˜ธ์ถœ ์ค‘</p>
      <p v-else-if="$fetchState.error">์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค</p>
      <div v-else>{{ user }}</div>
    </article>
    <button @click="fetchUser">๋‹ค์‹œ ํ˜ธ์ถœํ•˜๊ธฐ</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      user: {},
    }
  },
  methods: {
    fetchUser() {
      // fetch ์†์„ฑ์˜ ๋กœ์ง์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
      this.$fetch();
    }
  },
  async fetch() {
    const res = await axios.get('https://jsonplaceholder.typicode.com/users/1');
    this.user = res.data;
  },
  // ์•„๋ž˜ ์†์„ฑ์„ 'false'๋กœ ๋ฐ”๊พธ๋ฉด ์„œ๋ฒ„์—์„œ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•  ๋•Œ `fetch` ์†์„ฑ์˜ ๋กœ์ง์ด ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  fetchOnServer: false
}
</script>