본문 바로가기

책/Vue.js 퀵스타트

서버 사이드 렌더링

프로젝트 생성과 초기화

mkdir nuxttest
cd nuxttest
yarn init 또는 mpn init
yarn add nuxt@1.4.1 또는 npm install --save nuxt@1.4.1

package.json scripts 추가

  • nuxt : 개발 버젼으로 실행
  • nuxt build : Webpack을 이용해 애플리케이션을 빌드. 코드 난독화, 압축을 수행하므로 배포버전으로 적합
  • nuxt start : 운영버젼으로 서버를 시작 
  • nuxt generate : 애플리케이션을 빌드 후 모든 경로 화면을 정적 html 로 생성
{
  "name": "nuxttest",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "nuxt": "1.4.1"
  },
  "scripts": {
    "dev": "nuxt",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate"
  }
}

디렉터리 구조

디렉터리명  설명
assets less, sass, javascript와 같은 컴파일 되지 않는 자원들을 포함
components Vue.js 컴포넌트 포함
layouts 에플리케이션의 레이아웃 포함. 레이아웃은 .vue 파일로 작성
middleware 애플리케이션의 미들웨어를 포함. 이 디렉터리 아래에는 페이지나 레이아웃이 렌더링 되기 전에 실행할 함수들을 등록 가능.
pages 애플리케이션의 .vue 파일을 작성. 이 파일이 페이지이며, 단지 뷰만 제공하는 것이 아니라 하위 디렉터리 구성을 포함해 라우트 정보를 자동 생성
plugins Vue.js 애플리케이션이 생성되기 전 실행하고 싶은 자바스크립트 플러그인 포함
static 이미지, 텍스트 등의 정적파일 배치
store Vuex 저장소 파일 작성. Nuxt.js 애플리케이션이 실행되면서 자동으로 로딩
nuxt.config.js 사용자 정의 설정을 작성

저장소 기능 작성

constant.js

export default {
    CHANGE_NO: 'changeNo'
}

store/state.js

export default {
    no: 0,
    contacts: [
        { no: 1001, name: '김유신', tel: '010-1212-3331', address: '경주' },
        { no: 1002, name: '장보고', tel: '010-1212-3332', address: '청해진' },
        { no: 1003, name: '관창', tel: '010-1212-3333', address: '황산벌' },
        { no: 1004, name: '안중근', tel: '010-1212-3334', address: '해주' },
        { no: 1005, name: '강감찬', tel: '010-1212-3335', address: '귀주' },
        { no: 1006, name: '정몽주', tel: '010-1212-3336', address: '개성' },
        { no: 1007, name: '이순신', tel: '010-1212-3337', address: '통제영' },
        { no: 1008, name: '김시민', tel: '010-1212-3338', address: '진주' },
        { no: 1009, name: '정약용', tel: '010-1212-3339', address: '남양주' }
    ]
}

store/mutations.js

import Constant from '~/constant';
export default {
    [Constant.CHANGE_NO]: (state, payload) => {
        if (payload.no !== '') {
            state.no = payload.no;
        }
    }
}

store/getters.js

export default {
    getContactOne(state) {
        var no = state.no;
        var arr = state.contacts.filter(function(item, index) {
            return item.no == no;
        });
        if (arr.length == 1) return arr[0];
        else return {};
    },
    getContacts(state) {
        return state.contacts;
    }
}

store/index.js

  • Vuex를 사용할때 반드시 함수를 통해 Vuex 인스턴스를 리턴하도록 작성해야 한다는 점에 주의
//import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import getters from './getters';
import mutations from './mutations';
const store = () => {
    return new Vuex.Store({
        state,
        getters,
        mutations
    });
}
export default store;

nuxt.config.js 파일 작성

  • Nuxt.js 앱의 기능을 설정하는 역할을 수행
module.exports = {
    head: {
        link: [{
            rel: 'stylesheet',
            href: 'https://cdn.bootcss.com/bootstrap/3.3.1/css/bootstrap.css'
        }]
    }
}

레이아웃 작성

  • 레이아웃을 작성하지 않으면 기본 템플릿 app.html을 생성하여 메인레이아웃으로 사용
  • page 디렉터리 아래에 작성한 .vue 파일은 이 템플릿을 사용해 화면에 나타냄
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head>
    {{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>

layouts/default.vue

<template>
    <div>
        <div class="header">
            <h1 class="headerText">(주)OpenSSG</h1>
            <nav>
                <ul>
                    <li>
                        <nuxt-link to="/">Home</nuxt-link>
                    </li>
                    <li>
                        <nuxt-link to="/about">About</nuxt-link>
                    </li>
                    <li>
                        <nuxt-link to="/contacts">Contacts</nuxt-link>
                    </li>
                </ul>
            </nav>
        </div>
        <div class="container">
            <nuxt></nuxt>
        </div>
    </div>    
</template>
<script>
export default {
    name : 'default'
}
</script>
<style>
.header { background-color: aqua; padding: 10px 0 0 0;}
.headerText{ padding: 0px 20px 0px 20px;}
ul {
    list-style-type: none; margin:0; padding: 0;
    overflow:hidden; background-color: purple;
}
li { float: left;}
li a { 
    display: block; color:yellow; text-align: center;
    padding: 14px 16px; text-decoration: none;
}
li a:hover {background-color: aqua; color: black;}
</style>

페이지 작성

  • pages 디렉터리에 .vue 컴포넌트 파일을 작성하면 적절히 조합되어 뷰를 생성
  • Nuxt.js 에서의 .vue 컴포넌트 파일은 Vue 컴포넌이지만 유니버셜 애플리케이션 지원을 위해 추가적인 옵션을 제공
옵션명 설명
asyncData 페이지 컴포넌트가 로딩되기 전에 매번 호출되는 함수. 페이지 컴포넌트마다 비동기로 외부 데이터를 가져와야 할 경우에 유용
fetch 페이지가 렌더링 되기 전에 Vuex 저장소(store)에 데이터를 생성하기 위해 사용
head 페이지에 대한 특정 메타 태그를 설정하기 위해 사용
layout layouts 디렉터리에 정의된 특정 레이아웃을 사용하도록 지정
transition 페이지에 대한 트랜지션 효과 기능을 제공
validate 동적 라우트에 대한 유효성을 검증하는 기능을 제공
middleware 페이지나 레이아웃이 렌더링되기 저에 실행할 사용자 함수를 설정

정적라우트 경로

라우트 경로 디렉터리 및 파일 경로
/ ~/pages/index.vue
/about ~/pages/about.vue
/contacts ~/pages/contacts/index.vue
/contacts/:no ~/pages/contacts/_no.vue

pages/index.vue , about.vue

<template>
    <div>
        <h1>Home</h1>
    </div>    
</template>
<script>
export default {
    name: 'home'
}
</script>

pages/contacts/index.vue

<template>
    <div>
        <h1>연락처</h1>
        <div class="wrapper">
            <div class="box" v-for="c in contacts" :key="c.no">
                <nuxt-link :to="'/contacts/' + c.no">{{c.name}}</nuxt-link>
            </div>
        </div>
    </div>
</template>
<script>
import {mapGetters} from 'vuex';
export default {
    name : 'contacts',
    computed: mapGetters({
        contacts: 'getContacts'
    })
}
</script>
<style>
.wrapper { background-color: #fff; clear:both; display: table; }
.box {
    float: left; border-block-color: aqua; border-radius: 5px;
    padding: 10px; margin: 3px; text-align: center; font-size: 120%;
    width: 100px; font-weight: bold;
}
.a:link, a:visited {text-align: center; text-decoration: none; display: inline-block;}
</style>

pages/contacts/_no.vue

<template>
    <div>
        <h1>연락처 상세
            <div>
                <table class="detail table table-bordered">
                    <tbody>
                        <tr class="active">
                            <td>일련번호</td>
                            <td>{{contact.no}}</td>
                        </tr>
                        <tr class="active">
                            <td>이름</td>
                            <td>{{contact.name}}</td>
                        </tr>
                        <tr class="active">
                            <td>전화</td>
                            <td>{{contact.tel}}</td>
                        </tr>
                        <tr class="active">
                            <td>주소</td>
                            <td>{{contact.address}}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </h1>
    </div>
</template>
<script>
import {mapGetters} from 'vuex';
import {Constant} from '~/constant';
export default {
    name : 'contactbyno',
    computed : mapGetters({
        contact : 'getContactOne'
    }),
    created : function() {
        var no = this.$store.params.no;
        this.$store.commit(Constant.CHANGE_NO,{no: no});
    }
}
</script>
<style>
table.detail { width: 400px;}
</style>

npm run dev 실행

C:\JetBrains\vscode_workspace\nuxttest>npm run dev

> nuxttest@1.0.0 dev C:\JetBrains\vscode_workspace\nuxttest
> nuxt

  nuxt:build App root: C:\JetBrains\vscode_workspace\nuxttest +0ms
  nuxt:build Generating C:\JetBrains\vscode_workspace\nuxttest\.nuxt files... +4ms
  nuxt:build Generating files... +20ms
  nuxt:build Generating routes... +19ms
  nuxt:build Building files... +74ms
  nuxt:build Adding webpack middleware... +2s
  ████████████████████ 15% building modules{ parser: "babylon" } is deprecated; we now treat it as { parser: "babel" }.
  ████████████████████ 100%

Build completed in 3.737s



 DONE  Compiled successfully in -7259ms                                                                        15:16:04


 OPEN  http://localhost:3000

중첩라우트 작성

pages/contacts.vue

  • pages/contacts/index.vue -> pages/contacts.vue 파일 변경
<template>
    <div>
        <h1>연락처</h1>
        <div class="wrapper">
            <div class="box" v-for="c in contacts" :key="c.no">
                <nuxt-link :to="'/contacts/' + c.no">{{c.name}}</nuxt-link>
            </div>
        </div>
        <nuxt-child></nuxt-child>
    </div>
</template>

pages/contacts/_no/index.vue

  • pages/contacts/_no.vue -> pages/contacts/_no/index.vue 로 파일 변경
  • 변경하는 이유
    • /contact 로 요청했을 때는 연락처 상세가 나오지 않게 하기위해서
<template>
    <div>
        <hr class="divider"/>
        <h1>연락처 상세</h1>
        <div>
            <table class="detail table table-bordered">
...            
            </table>
        </div>        
    </div>
</template>
<script>
import {mapGetters} from 'vuex';
import Constant from '~/constant';
export default {
    name : 'contactbyno',
    computed : mapGetters({
        contact : 'getContactOne'
    }),
    created : function() {
        var no = this.$store.params.no;
        this.$store.commit(Constant.CHANGE_NO,{no: no});
    },
    beforeRouteUpdate(to, form, next) {
        var no = to.params.no;
        this.$store.commit(Constant.CHANGE_NO, {no: no});
        next()
    }
}
</script>
<style>
table.detail { width: 400px;}
</style>

트랜지션 호과 적용

assets/transition.css

.page-enter-active,
.page-leave-active {
    transition: .3s;
}

.page-enter,
.page-leave-to {
    opacity: 0;
}

.elastic-enter-active {
    animation: elastic-in .5s;
}

.elastic-leave-active {
    animation: elastic-in .5s reverse;
}

@keyframes elastic-in {
    0% {
        transform: scale(0);
        opacity: 0;
    }
    50% {
        transform: scale(1.2);
        opacity: 0.5;
    }
    100% {
        transform: scale(1);
        opacity: 1;
    }
}

nuxt.config.js

module.exports = {
    head: {
...       
    },
    css: ['assets/transition.css']
}

pages/contacts.vue

<script>
import {mapGetters} from 'vuex';
export default {
    name : 'contacts',
    transition : 'elastic',
    ...
}
</script>

' > Vue.js 퀵스타트' 카테고리의 다른 글

단위 테스트  (0) 2020.11.28
트랜지션 효과  (0) 2020.11.28
vue-router를 이용한 라우팅  (0) 2020.11.28
Vuex를 이용한 상태관리  (0) 2020.11.27
axios를 이용한 서버통신  (0) 2020.11.25