Nuxt.jsで静的サイトを作る
Middlemanで制作していたサイトのソースを紛失したので、いっその事Nuxtで作り直した話。
ちなみに作成したのは学校でやってる国際ワークショップのサイト。ここから見られます。今後、自分が卒業したあと別の担当者が管理する可能性があるので、極力タグの記述のみでスタイルが適用されることと、ヘッダーやフッターなどの年号を設定ファイルでまとめて管理するなど気を配りました。
雛形作成
まずNuxt.js公式に従ってcliから雛形を生成しましょう。パッケージ管理はyarnを使います。
vue init nuxt-community/starter-template iweee
cd iweee
yarn install
今回はCSSフレームワークにMaterial Design Liteを使います。その他にも必要なパッケージ類をインストールしましょう。
yarn add material-design-lite node-sass sass-loader typicons.font
以上で、コーディングの準備が整いました。
ファビコン
ファビコンを生成しましょう。realfavicongenerator.netが使いやすいのでおすすめです。生成したファビコンはstatic
ディレクトリに配置しましょう。
サイト設定
ヘッダーやフッターに記載されている開催年やCopyrightはまとめて設定ファイルで管理します。まずその設定ファイルを作成しましょう。また、このサイトはサブディレクトリで運用されるのでルートからのパスも設定します。
{
"year": "2017",
"copyrightYear": "2017",
"serverPath": "/iweee/2017/"
}
続いて、サイト全体の設定や<head>
タグに関する設定はプロジェクトルートのnuxt.config.js
で行います。ここで、先程の設定ファイルの内容を読み込みましょう。Webpackはrequire
でjsonを指定すると自動的にObjectとしてロードしてくれるので便利です。
const { resolve } = require('path');
const iweee = require('./iweee.config.json');
module.exports = {
head: {
titleTemplate: `%s - IWEEE:International Workshop on Effective Engineering Education ${iweee.year}`,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: `IWEEE:International Workshop on Effective Engineering Education ${iweee.year} Official WebSite` },
{ name: 'theme-color', content: '#ffffff' }
],
link: [
{ rel: 'apple-touch-icon', sizes: '180x180', href: 'apple-touch-icon.png' },
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: 'favicon-32x32.png' },
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: 'favicon-16x16.png' },
{ rel: 'manifest', href: '/manifest.json' },
{ href: 'https://fonts.googleapis.com/icon?family=Material+Icons', rel: 'stylesheet' },
{ href: 'https://fonts.googleapis.com/css?family=Roboto:300,400', rel: 'stylesheet' }
],
},
css: [
{ src: 'material-design-lite/src/material-design-lite.scss', lang: 'scss' },
{ src: 'typicons.font/src/font/typicons.css' },
],
plugins: [ '~/plugins/global-mixins.js', '~/plugins/global-components.js' ],
loading: { color: '#3B8070' },
build: {
extend (config, ctx) {
if (ctx.dev && ctx.isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
},
vendor: [ 'material-design-lite/material.min.js' ]
},
generate: {
dir: resolve(__dirname, './dist' + iweee.serverPath),
},
router: {
base: process.env.NODE_ENV === 'dev' ? '/' : iweee.serverPath,
}
}
head
要素は書いた内容がそのまま<head>
タグに出力されるので説明は省きます。
css
要素は前ページで利用したいスタイルを設定します(公式リファレンス)。node_modules
のパッケージから読み込むときはパスをパッケージ名で始めてください。プロジェクト内のファイルを読み込む場合はプレフィックス~
をつけたパスにしてください。
plugin
要素は後述しますがNuxtプラグインを読み込みます。
build
要素はビルドの設定です。ここで追加しているのはvendor
要素です。各ページやコンポーネントでnode_modules
からのソースを読み込むとNuxtは各ページごとにビルドし、おなじソースコードが大量にpackされます。そこで全ページで利用するnode_modules
からのソースはここに記述することになっています(公式リファレンス)。
generate
要素は静的サイトとして生成するときの設定です。ここでは設定されたサブディレクトリで出力されるようにします。
router
要素はvue-routeの設定です。ここもサブディレクトリ運用のための設定です。開発時はルートで、運用時はサブディレクトリでリンクが生成されます。
プラグイン
Nuxtを書いていると、Vue.component
だったりVue.use
を使いたくなります。そういうときはプラグインを使います。このサイトではサイト設定の注入と全ページで利用したいコンポーネントの読み込みに使ってます。
サイト設定の注入にはVue.mixin
を使いました。
import Vue from 'vue'
import iweee from '../iweee.config.json'
Vue.mixin({
data () {
return {
iweee: iweee
}
}
})
グローバルコンポーネントはおなじみVue.component
です。
import Vue from 'vue'
import PageArticle from '~/components/PageArticle.vue'
Vue.component('PageArticle', PageArticle)
レイアウトと基本的なコンポーネント
続いて、レイアウトと基本的なコンポーネントを作成しましょう。
基本コンポーネントはドロワー、ヘッダーとフッターの合計3コンポーネントです(勝手に決めた)。これらとコンテンツをレイアウトで結合します。
レイアウト
実際のレイアウトファイルです。各コンポーネントをロードして配置しているだけなのでシンプルです。コンテンツは<nuxt>
タグに展開されます。
<template>
<div>
<div class="mdl-layout mdl-js-layout mdl-layout--fixed-drawer mdl-layout--fixed-header">
<NavigationHeader/>
<NavigationDrawer/>
<div class="mdl-layout__content">
<nuxt/>
<NavigationFooter />
</div>
</div>
</div>
</template>
<script>
import NavigationDrawer from '~/components/NavigationDrawer.vue'
import NavigationHeader from '~/components/NavigationHeader.vue'
import NavigationFooter from '~/components/NavigationFooter.vue'
export default {
components: {
NavigationDrawer,
NavigationHeader,
NavigationFooter
}
}
</script>
ドロワー
画面の左端にあるやつです。Nuxtはページ切り替え時にDOMを再構築しないので速いみたいですが、MDLはそんなこと想定してないのでモバイル表示の時にドロワーのリンクをクリックすると新しいページに遷移してもドロワーが開いたままになります。そこで<nuxt-link>
のクリックイベントをフックして強制的に閉じます(mdl側にはtoggleDrawerしかなかったのでコピーして閉じる動作だけにしてあります)。あと@click.native
じゃないとnuxt側にイベント持ってかれちゃって取れないです。
<template>
<div class="mdl-layout__drawer">
...
<nav class="mdl-navigation">
<nuxt-link to="/" class="mdl-navigation__link typcn typcn-home iweee-navigation__link--home" @click.native="closeDrawer">Home</nuxt-link>
...
</nav>
</div>
</template>
<script>
export default {
methods: {
closeDrawer: () => {
document.querySelector('.mdl-layout__obfuscator').classList.remove('is-visible')
document.querySelector('.mdl-layout__drawer').classList.remove('is-visible')
document.querySelector('.mdl-layout__drawer').setAttribute('aria-hidden', 'true')
document.querySelector('.mdl-layout__drawer-button').setAttribute('aria-expanded', 'false')
}
}
}
</script>
<style scoped>
...
</style>
ヘッダー、フッター
特に説明しなくても大丈夫だよね…?
<template>
<header class="mdl-layout__header mdl-layout__header--transparent">
<div class="mdl-layout__header-row mdl-shadow--2dp">
<span class="mdl-layout-title">
<img src="~/assets/img/iweee-logo-line.png">
</span>
</div>
</header>
</template>
<style>
.mdl-layout__drawer-button {
color: black !important;
}
.mdl-layout-title {
display: block !important;
position: absolute;
left: calc(50% - 68px);
top: 12px;
}
.mdl-layout-title img {
width: 150px;
}
@media all and (min-width: 1025px) {
.mdl-layout__header {
display: none;
}
}
</style>
<template>
<footer class="copyright mdl-typography--text-center mdl-color-text--grey-600"><small>© National Institute of Technology, Kisarazu College {{ iweee.copyrightYear }}</small></footer>
</template>
<style>
.copyright {
position: relative;
top: -50px;
}
</style>
ページコンポーネントとコンテンツ作成
ページコンポーネント作成
ページのレイアウトをコンポーネントに押し込めましょう。名前付きスロットでやりましょう。そうそう、<slot>
の子要素に記述するとデフォルト値になるってこと初めて知りました。
ここでもNuxtとMDLの相性問題で、ページが遷移してもスクロール位置がリセットされません。本来はscrollToTop:true
でできるんですが、うまく動きません(公式リファレンス)。また無理やりmounted
でスクロール位置をリセットします。
<template>
<div>
<div class="ribbon">
<slot name="ribbon-img"><img src="~/assets/img/sogo-blur.png"></slot>
</div>
<div class="mdl-grid">
<div class="mdl-cell mdl-cell--1-col mdl-cell--hide-tablet mdl-cell--hide-phone"></div>
<article class="container mdl-cell mdl-cell--10-col mdl-shadow--4dp mdl-color--white">
<div class="title">
<slot name="title"></slot>
</div>
<div class="content mdl-color-text--grey-700 ">
<slot name="content"></slot>
</div>
</article>
</div>
</div>
</template>
<script>
export default {
mounted () {
window.componentHandler.upgradeDom()
document.querySelector('.mdl-layout__content').scrollTop = 0
}
}
</script>
...
コンテンツ作成
さあ、準備が整ったのであとはコンテンツを作成しましょう。Nuxtはpages
ディレクトリにVueファイルを作成すれば自動的に認識してレイアウトとルーティングを作成します。
一番シンプルなページのサンプルを載せておきます。
<template>
<PageArticle>
<h4 slot="title">Program Schedule</h4>
<div slot="content">
<div class="steps">
<section class="step">
<h4 class="step-head">Wednesday, 6th December 2017</h4>
<div class="step-content">
<p>Oral Presentation</p>
</div>
</section>
<section class="step">
<h4 class="step-head">Thursday, 7th December 2017</h4>
<div class="step-content">
<p>Students' Poster Presentation</p>
</div>
</section>
</div>
<p class="mdl-typography--text-center">(details will be updated later)</p>
</div>
</PageArticle>
</template>
<script>
export default {
head: {
title: 'Program Schedules'
}
}
</script>
さっき、nuxt.config.js
でtitle
に%s
が紛れてたと思いますが、各ページのscript
にhead
のtitle
要素に値を設定すると置き換えます。便利です。
staticディレクトリの行方
webpackで処理しないファイルはstatic
ディレクトリに保存しますがビルドすると、直下にすべて展開されるのでstatic
の中へのリンクは~static/hoge
ではなくhoge
です。意外とつまづきます。
ビルド
全部終わったら、静的サイトを生成しましょう。
yarn run generate
最後に
SPAにもいいけど、普通のサイト制作にも使えるやつです。いわゆるユニバーサルJSで書かなきゃいけないことがありますが、慣れれば楽です。
Nuxt.js初めて使ったけど、セクシーです。みんな使おう!