ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Nuxt.js] Nuxt 학습기 - (3) Views, Routing, Middleware & Validate
    Front-End(Web)/Vue 2021. 11. 29. 02:17
    반응형

    Nuxt 학습기의 3번째 시리즈부터는 Nuxt.js 토이 프로젝트를 진행하면서 사용해본 주요기능들을 정리해보려고 한다!

    기능들이 전체적으로 유기적으로 연결되어 있어, 관련성보다는 Nuxt.js 가이드에서 소개하는 내용들 기반으로 진행해보려고 한다.


    📗 Views

    Nuxt.js 어플리케이션의 기본적인 Viewing 구조를 먼저 짚고 넘어가보자.

     

    1. Document

    Nuxt.js 어플리케이션 전체에 해당하는 HTML 도큐먼트이자 파일이다.

     

    2. Layouts

    모든 페이지에 공통으로 사용되는 레이아웃, 혹은 사용자 정의 레이아웃을 만들 수 있다. Header 등 공통요소를 반영하는데 용이하다.

    /layouts/default.vue 파일이 기본적으로 생성되며, 여기에 사용자가 커스텀도 적용할 수 있다.

    <template>
      <div id="app">
        <Header />
        <Nuxt id="page" />
        <Footer />
      </div>
    </template>
    
    <script>
    import Header from '@/components/common/Header'
    import Footer from '@/components/common/Footer'
    
    export default {
      components: {Header, Footer},
    }
    </script>
    
    <style lang="scss">
    #app {
      #page {
        padding: 60px 0;
      }
    }
    </style>

     

    또한, /layouts 에 사용자 정의 레이아웃 Vue 파일을 생성하고, 이를 컴포넌트의 layout 프로퍼티로 적용할 수 있다.

    <script>
    export default {
      // ...
      layout: 'blog'
    }
    </script>

     

    * 에러 페이지

     

    validate() 메서드 등으로 인한 404 에러, 혹은 500 에러가 발생했을 때 자동으로 표현할 페이지 컴포넌트에 해당한다.

    /layouts/error.vue 에 제작하며, 별도로 <nuxt /> 태그를 포함하지는 않는다.

    <template>
      <div error-page>
        <p>Here Is Error Page!</p>
        <button @click="goHome">back home</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "ErrorPage",
      methods: {
        goHome() {
          this.$router.push("/");
        },
      },
    };
    </script>
    
    <style lang="scss">
    [error-page] {
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      height: calc(100vh - 180px);
      background: #dd1043;
    
      p {
        margin: 0 0 20px;
        color: #fff;
        font-size: 30px;
        font-weight: bold;
      }
    }
    </style>

     

    3. Pages (페이지)

    모든 Pages 컴포넌트는 Vue 컴포넌트이다. 단, Nuxt.js는 Pages 컴포넌트에서 활용할 수 있는 기능들을 제공하고 있다.

    <template>
      <h1 class="red">Hello {{ name }}!</h1>
    </template>
    
    <script>
    export default {
      asyncData (context) {
        // called every time before loading the component
        return { name: 'World' }
      },
      fetch () {
        // The fetch method is used to fill the store before rendering the page
      },
      head () {
        // Set Meta Tags for this Page
      },
      // and more functionality to discover
      ...
    }
    </script>
    
    <style>
    .red {
      color: red;
    }
    </style>
    속성 설명
    asyncData 가장 중요한 키며 비동기적으로 데이터를 받을 수 있고, 첫 번째 인자로 전달 받을 수도 있습니다. 자세한 내용 및 어떻게 동작하는 지는 이곳에서 확인합니다.
    fetch 페이지가 렌더링되기 전에 스토어를 채우기위해 사용되며, 구성 요소 데이터를 설정하지 않는다는 점을 제외하면 데이터 메소드와 같습니다. 자세한 내용은 이곳에서 확인합니다.
    head 현재 페이지에 대한 특정 메타 태그를 설정하려면 이곳에서 확인합니다.
    layout layouts 폴더에 정의된 레이아웃을 지정할 수 있습니다. 자세한 내용은 이곳에서 확인합니다.
    transition 페이지에 대한 특정 트랜지션을 설정합니다. 자세한 내용은 이곳에서 확인합니다.
    scrollToTop 기본값은 false 입니다. 페이지를 렌더링하기 전에 페이지를 맨 위로 스크롤할 것인지를 나타내며, 중첩 라우트를 위해 사용됩니다. 자세한 내용은 이곳에서 확인합니다.
    validate 동적 라우트에 대한 유효성을 검사합니다. 자세한 내용은 이곳에서 확인합니다.
    middleware 이 페이지에 대한 미들웨어를 설정하면, 미들웨어는 페이지를 렌더링하기 전에 호출되며, 자세한 내용은 이곳에서 확인합니다.

    asyncData, fetch 등 SSR 요청과, validate, middleware 등 검증 메서드들은 별도로 정리하고 Head 속성만 우선 정리해보겠다.

     

    * Head 메서드

    해당 페이지의 타이틀(브라우저 탭에 표현되는), meta 데이터들 등을 설정할 수 있다.

    export default {
      head () {
        return {
          title: '타이틀',
          meta: [
            { 
              hid: '유니크한 아이디', 
              name: '설명', 
              content: '커스텀한 설명' 
            }
          ]
        }
      }
    }

    📗Routing (라우팅)

    Nuxt.js 프로젝트의 pages 디렉토리에는 페이지 컴포넌트들이 들어간다.

    이 때, 디렉토리 구조가 어플리케이션의 라우팅 구조로 직결되며, 이를 기반으로 Router가 형성된다. (.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 _5edf9a1b = () => interopDefault(import('../pages/detail/_id.vue' /* webpackChunkName: "pages/detail/_id" */))
    const _3cab7b83 = () => 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: "/detail/:id?",
        component: _5edf9a1b,
        name: "detail-id"
      }, {
        path: "/",
        component: _3cab7b83,
        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.js 역시 Vue Router 처럼, Basic Route(기본 라우팅)과 Dynamic Route(동적 라우팅, 파라미터) 2가지 종류가 있다.

     

    - Basic Route (기본 라우팅)

    기본 라우팅은 /pages 디렉토리에 파일 혹은 디렉토리만 생성해주면 자동으로 라우팅이 된다.

    디렉토리의 path 값이 곧 도메인(URL)의 path 값이 되는 것이다.

    // File Tree
    pages/
    --| user/
    -----| index.vue
    -----| one.vue
    --| index.vue
    
    // Router
    router: {
      routes: [
        {
          name: 'index',
          path: '/',
          component: 'pages/index.vue'
        },
        {
          name: 'user',
          path: '/user',
          component: 'pages/user/index.vue'
        },
        {
          name: 'user-one',
          path: '/user/one',
          component: 'pages/user/one.vue'
        }
      ]
    }

     

    * 중첩 라우팅

    중첩 라우팅이란, 위처럼 특정 도메인(/container)의 레이아웃 내에서 동적으로 변하는 화면(Content1, Content2)을 만들기 위한 방법이다.

    Container 컴포넌트에 <nuxt-child /> 태그를 적용하면, 해당 부분에 Child 컴포넌트들이 path에 따라 동적으로 렌더링된다.

    Router 에서도, Container 컴포넌트의 path에 children 프로퍼티로 Content1, Content2 컴포넌트들을 자동으로 추가해준다.

    <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>
    // ...
    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"
      }]
    // ...

     

    - Dynamic Route (동적 라우팅)

    // File Tree
    pages/
    --| _slug/
    -----| comments.vue
    -----| index.vue
    --| users/
    -----| _id.vue			// id: Params
    --| index.vue
    
    // Router
    router: {
      routes: [
        {
          name: 'index',
          path: '/',
          component: 'pages/index.vue'
        },
        {
          name: 'users-id',
          path: '/users/:id?',
          component: 'pages/users/_id.vue'
        },
        {
          name: 'slug',
          path: '/:slug',
          component: 'pages/_slug/index.vue'
        },
        {
          name: 'slug-comments',
          path: '/:slug/comments',
          component: 'pages/_slug/comments.vue'
        }
      ]
    }

    상세 페이지와 같이, 특정값(대개 id)에 따라 내용이 바뀌어야하는 페이지에 많이 사용되는 방법이다.

    Pages 컴포넌트 내에서, _(언더바) + [param](변수명).vue 파일을 만들면 여기에 동적 라우팅이 적용되며, 변수명을 route의 params 속성을 통해 가져올 수 있다.

     

    * validate() : 동적 라우팅 유효성 체크하기

    // pages/users/_id.vue
    
    export default {
      validate ({ params }) {
        // Must be a number
        return /^\d+$/.test(params.id)
      }
    }

    Nuxt.js의 validate() 메서드와, 해당 페이지에서 가져오는 params 데이터를 통해 해당 페이지로 라우팅하기 전 유효성 검사를 진행할 수 있다. (false가 반환되면, 404 error 페이지로 이동)


    📗Middlewares & Plugins

     

    - Middlewares (미들웨어)

    모든 pages 또는 layouts를 렌더링하기 전에 실행하는 파일들의 집합(폴더)을 의미한다. 이는, 서버에도 통용되는 개념이다.

    이 미들웨어가 해결되기 전까지, 브라우저는 아무것도 표현하지 않는다. Vuex 로그인 검증, 매개변수 유효성 검사 등에 유용하다.

    // middleware/authenticated.js
    
    export default async function({ app, route }) {
        // 접속한 사용자의 권한 레벨
        let level = app.$auth.user.level
        let pageLevel = route.matched[0].components.default.options.level
        
        // 접속하려는 페이지의 레벨보다 낮은 레벨을 가지고 있는 경우
        if (level < pageLevel) {
            // 페이지 접속 불가
            return
        }
    }

    * 출처 : https://webinformation.tistory.com/107

     

    Nuxt.js 프로젝트는 middleware/ 디렉토리를 기본적으로 지원하며, 여기에 존재하는 파일들이 미들웨어가 된다. (파일명 = 미들웨어명)

    파일 내 정의한 메서드 자체가 하나의 middleware가 되며, 인자로는 context를 받는다. 예시처럼, 미들웨어는 비동기도 가능하다.

    (context에서는, app, route, store, req, error, redirect, isServer, isClient 등의 프로퍼티를 활용할 수 있다.)

     

    미들웨어는, 1) nuxt.config.js  ➡️  2) Layout 컴포넌트 프로퍼티  ➡️  3) Page 컴포넌트 프로퍼티 순으로 실행된다.

    // 1) nuxt.config.js 설정방법
    
    module.exports = {
      router: {
        middleware: 'middleware name'	// String or Array
      }
    }
    
    
    // 2) Layout, 3) Pages 컴포넌트 설정방법
    <script>
    export default {
      // ...
      middleware: 'authenticated'
    }
    </script>

     

     

    - Plugins(플러그인)

    Nuxt.js 역시 프로젝트에서 공통으로 사용할 플러그인(기능, 메서드 등)을 등록할 수 있다. 이는, Vue 인스턴스 형성 전에 적용된다.

    이렇게 생성된 플러그인들은, 어플리케이션 전체에서 공유 및 액세스가 가능하다.

    // [생성] : plugins/vue-notifications.js
    
    import Vue from 'vue'
    import VueNotifications from 'vue-notifications'
    
    Vue.use(VueNotifications)
    
    
    // [적용] : nuxt.config.js
    
    module.exports = {
      build: {
        vendor: ['vue-notifications']
      },
      plugins: ['~plugins/vue-notifications']
    }

    Nuxt.js 프로젝트는 plugins/ 디렉토리 역시 기본으로 포함하고 있다. 여기서, 각 플러그인을 생성해주면 된다.

    그리고, nuxt.config.js 의 plugins 옵션에서 해당경로를 추가해주면 된다.

     

    vue-notifications 같은 경우는 라이브러리이므로, app번들에 포함하는 것보다 vendor번들에 캐싱시켜 반복적인 import를 줄인다.

    아래, axios 역시 대표적으로 vendor 번들시키는 패키지이다. 이렇게 처리하면, 페이지 번들 중복을 줄이며 어디서든 import가 가능하다.

    module.exports = {
      build: {
        vendor: ['axios']
      }
    }

     

    * $root 컴포넌트 & context 에 병합하기

     

    vue-i18n(다국어 적용 라이브러리) 와 같은 일부 플러그인들은, App 루트에서 사용하기 위해 루트 컴포넌트에 주입되야 한다.

    $root Component 및 context 객체에 병합시키기 위해서, injectAs 프로퍼티를 활용한다.

    // [생성] : plugins/i18n.js
    
    import Vue from 'vue'
    import VueI18n from 'vue-i18n'
    import store from '~store'
    
    Vue.use(VueI18n)
    
    const i18n = new VueI18n({
      /* options... */
    })
    
    export default i18n
    // [적용(주입)] : nuxt.config.js
    
    module.exports = {
      build: {
        vendor: ['vue-i18n']
      },
      plugins: [
        // Will inject the plugin in the $root app and also in the context as `i18n`
        { src: '~plugins/i18n.js', injectAs: 'i18n' }
      ]
    }

    Next.js와 마찬가지로, Nuxt.js도 Pages 디렉토리 스트럭쳐를 기반으로 Router가 형성되었다.

    또한, Layout부터 Router, Store 등등 다양한 기능들을 지원하기 때문에, 이러한 기능들을 이번 토이에 사용해보고 이를 다시금 공부하면서 복기 및 정리하는 포스팅을 진행하고 있는 것이다.

     

    확실히, Nuxt.js 는 Vue.js 프레임워크를 기반으로 하고있다보니, 전역상태 Store부터 middleware, validate같은 검증 메서드 등 다양한 기능들을 제공하고 있다.

    무엇보다, transition 같은 페이지 전환효과까지 커스텀할 수 있는 부분에서, 프레임워크와 Nuxt.js의 장점을 강하게 느낄 수 있었다.

     

    아직, 정리하지못한 기능들이 남아있다. Vuex Store와 더불어, 어쩌면 SSR에서 가장 중요한 최초 비동기 데이터 요청인 asyncData, fetch 등을 다음 포스팅에 정리하려고 한다!

     

    [참고]

    - [Nuxt.js 미들웨어] Nuxt.js 가이드북 : https://develop365.gitlab.io/nuxtjs-0.10.7-doc/ko/guide/routing/#middleware  

    - [Nuxt.js플러그인] Nuxt.js 가이드북 : https://develop365.gitlab.io/nuxtjs-0.10.7-doc/ko/guide/plugins/

    - [Nuxt.js 전체] doozi0316 님의 블로그 : https://doozi0316.tistory.com/entry/Nuxtjs-%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%98%88%EC%A0%9C-SSR-CSR-Universal  

     

    반응형
Designed by Tistory.