いいオフィスが無料で使える!
いいオフィスが無料で使える!
2019.03.05
#3
Nuxt de Portfolio

【Ch.2】プロジェクトを作成する【Nuxt de Portfolio】

ライダー

こんにちは、フロントエンドエンジニアのライダーです。

今回は「Nuxt de Portfolio」連載のチャプター2となります。

前回はNuxt をインストールし、実際に動かしながらその考え方を学んでいきました。続く今回の記事では、いよいよポートフォリオサイトのプロジェクトを作成していきます。

つくるものを確認

今回つくるページは、

  1. Index ページ
  2. About ページ
  3. 404 ページ

の3ページとしましょう。

プロジェクト作成

前回作成したプロジェクトは使いまわさないので削除して問題ありません。

npx create-nuxt-app my-portfolio

上記コマンドでプロジェクトを作成します。

動作確認をします。

cd my-portfolio
npm run dev

前回同様に Nuxt ロゴが表示されれば問題ありません。

ヘッダー・フッターを作成する

今回、Index ページ・About ページ両方にヘッダーとフッターが存在しますので、ヘッダーコンポーネントと、フッターコンポーネントを作成しましょう。

ヘッダーやフッターは、レイアウトコンポーネントでもページコンポーネントでもないため、 components/ 内に作成します。その内部でさらに common ディレクトリを作成しました。

components/common/TheHeader.vue

<template>
	<header class="header">
		<div class="header-inner">
			<h1>my-portfolio</h1>
			自己紹介
		</div>
	</header>
</template>

components/common/TheFooter.vue

<template>
	<footer class="footer">© my-portfolio.</footer>
</template>

ここで命名をThe~~.vue としましたが、これは、ページ内にひとつだけ存在するコンポーネントに使う命名です。詳しくは Vue.js スタイルガイド(https://jp.vuejs.org/v2/style-guide/index.html #単一インスタンスのコンポーネント名)を読んでみてください。

さて、これらを共通パーツとしてレイアウトコンポーネントに組み込んでいきます。

layouts/default.vue を開いてください。

元あったコードはすべて削除して大丈夫です。

<template>
  <div>
    <TheHeader/>
    <main class="contents">
      <nuxt/>
    </main>
    <TheFooter/>
  </div>
</template>

<script>
  import TheHeader from '~/components/common/TheHeader';
  import TheFooter from '~/components/common/TheFooter';

  export default {
    components: {
      TheHeader,
      TheFooter
    }
  }
</script>

こんな感じで、上下にヘッダー、フッターが表示されたかと思います。

切り分けて解説

import TheHeader from '~/components/common/TheHeader';
import TheFooter from '~/components/common/TheFooter';

コンポーネントファイルの読み込みを行なっています。~ は、ソースディレクトリを意味し、デフォルトのままだとプロジェクトルートと同じ階層を表します。

読み込んだコンポーネントを、 default.vue の中で使えるようにするために、名前付きで指定します。

export default {
	components: {
		TheHeader,
		TheFooter
	}
}

components: {} で指定したコンポーネントはテンプレート内で使うことができるようになります。

<div>
  <TheHeader/>
  <main class="contents">
    <nuxt/>
  </main>
  <TheFooter/>
</div>

コンポーネントの一番外側の要素はひとつになっていなければいけないルールが存在するため(複数あるとエラー)、3要素をひとつの <div></div> で包んでいます。

<nuxt/> 部分は、URLに合わせたページコンポーネントが切り替わる要素です(ページコンポーネントはそのディレクトリ・ファイル名がURLを定義するんでしたね)!

修正・追加・削除等があったページ

  • [修正] layouts/default.vue
  • [追加] components/TheHeader.vue
  • [追加] components/TheFooter.vue

Index・About・404 ページを新規作成する

それでは、デフォルトのままになっているコンテンツ部分を修正していきましょう。ここでは中身がどうなっているかは重要ではないので、気軽にいきましょう。

Index ページ作成

pages/index.vue を開いてください。

こちらも、デフォルトのものは全て削除してしまいます。下記をコピー&ペーストしてください。 <script><style> はまだ書きません。

<template>
  <div class="page-index">
    <section class="about">
      <h2>夏目漱石</h2>
      <div class="article-body">
        <img src="~/assets/images/portrait.png" alt="夏目漱石">
        <p>日本の小説家、評論家、英文学者。本名、夏目 金之助(なつめ きんのすけ)。江戸の牛込馬場下横町(現在の東京都新宿区喜久井町)出身。俳号は愚陀仏。詳細は<a href="https://ja.wikipedia.org/wiki/夏目漱石" target="_blank">ウィキペディア</a>を。</p>
      </div>
    </section>

    <section class="works">
      <h2>代表作</h2>
      <div class="article-body">
        <p>『吾輩は猫である』(1905年)がデビュー作である。</p>
        <ul>
          <li>『吾輩は猫である』(1905年)</li>
          <li>『坊っちゃん』(1906年)</li>
          <li>『草枕』(1906年)</li>
          <li>『三四郎』(1908年)</li>
        </ul>
      </div>
    </section>
  </div>
</template>

<img src=""> には、 TheHeader.vue コンポーネントを読み込んだときと同様に、 ~ を利用していることに注意です。

ちなみに、画像は適当なものをご準備ください。

About ページ作成

はい、ガンガンいきます。次にAbout ページを作成します。pages/about/index.vue作成してください。

<template>
  <div class="page-about">
    <section class="about">
      <h2>はじめまして</h2>
      <div class="article-body">
        <p>夏目 漱石(なつめ そうせき、1867年2月9日(慶応3年1月5日) - 1916年(大正5年)12月9日)は、日本の小説家、評論家、英文学者。本名、夏目 金之助(なつめ きんのすけ)。江戸の牛込馬場下横町(現在の東京都新宿区喜久井町)出身。俳号は愚陀仏。</p>
        <p>大学時代に正岡子規と出会い、俳句を学ぶ。帝国大学(後の東京帝国大学、現在の東京大学)英文科卒業後、松山で愛媛県尋常中学校教師、熊本で第五高等学校教授などを務めた後、イギリスへ留学。帰国後、東京帝国大学講師として英文学を講じながら、「吾輩は猫である」を雑誌『ホトトギス』に発表。これが評判になり「坊っちゃん」「倫敦塔」などを書く。</p>
        <p>その後朝日新聞社に入社し、「虞美人草」「三四郎」などを掲載。当初は余裕派と呼ばれた。「修善寺の大患」後は、『行人』『こゝろ』『硝子戸の中』などを執筆。「則天去私(そくてんきょし)」の境地に達したといわれる。晩年は胃潰瘍に悩まされ、「明暗」が絶筆となった。</p>
      </div>
    </section>

    <section class="history">
      <h2>経歴</h2>
      <div class="article-body">
        <h3>幼少期</h3>
        <p>1867年2月9日(慶応3年1月5日)、江戸の牛込馬場下に名主・夏目小兵衛直克、千枝の末子(五男)として出生。父・直克は江戸の牛込から高田馬場一帯を治めている名主で、公務を取り扱い、大抵の民事訴訟もその玄関先で裁くほどで、かなりの権力を持っていて、生活も豊かだった[5]。</p>
        <h3>正岡子規との出会い</h3>
        <p>1889年(明治22年)、同窓生として漱石に多大な文学的・人間的影響を与えることになる俳人・正岡子規と初めて出会う。子規が手がけた漢詩や俳句などの文集『七草集』が学友らの間で回覧されたとき、漱石がその批評を巻末に漢文で書いたことから、本格的な友情が始まる。</p>
        <h3>イギリス留学</h3>
        <p>1893年(明治26年)、漱石は帝国大学を卒業し、高等師範学校の英語教師になるも、日本人が英文学を学ぶことに違和感を覚え始める。</p>
        <h3>作家への道と朝日新聞社入社</h3>
        <p>英国留学から帰国後、1903年(明治36年)3月3日に、本郷区駒込千駄木町57番地に転入(現在の文京区向丘2-20-7、千駄木駅徒歩約10分。現在は日本医科大学同窓会館、敷地内に記念碑あり。)。同月末、籍を置いていた第五高等学校教授を辞任。</p>
      </div>
    </section>
  </div>
</template>

とくに説明不要かと思いますが、どのコンポーネントももれなくルートになる要素はひとつ(今回は div.page-about)というルールだけは守ってください。

404 ページ作成

次はそのページ自体はURLを持たない、404エラーページを作成します。 layouts/error.vue を作成してください。

<template>
  <div class="layout-error">
    <section class="error">
      <h2>404</h2>
      <div class="article-body">
        <h3>名前はまだ無い</h3>
        <p>アクセスされたページは存在しないか、すでに削除されています。</p>
      </div>
    </section>
  </div>
</template>

このとき、 nuxt.config.js に、

generate: {
	fallback: true
},

を、下記のように追記してください。これは、Ch5 で説明します。

const pkg = require('./package')

module.exports = {
	generate: {
		fallback: true
	},
    mode: 'universal',
			・
			・

ここまでで、Index, About, 404 の計3ページを作成しました。表示確認をしましょう。

npm run dev でローカルサーバが立ち上がります。立ち上げが終わったら

それぞれ、表示されるか確かめてみてください。

全ページ、上下に共通のヘッダー、フッターがあり、中身がURLによって差し代わっているかと思います。

修正・追加・削除等があったページ

  • [修正] nuxt.config.js
  • [追加] assets/images/portrait.png
  • [追加] layouts/error.vue
  • [修正] pages/index.vue
  • [追加] pages/about/index.vue

スタイルを追加する

ここまで、HTML( <template> )だけを追加してきましたが、適当なスタイルを用意していきましょう。

とはいっても難しいことはなく、素直に <style> の中にCSSを記述していくことができます。

とはいえ、このようにコンポーネントが相互にスタイルの影響を及ぼしあう環境があると、意図せず崩れてしまったり、深いCSS設計への知見が必要になります。

Nuxt では、コンポーネント志向で開発を進めることはすでにお話ししました。

しかし、Nuxtでプロジェクトを作成した場合に、scoped という属性を付与し <style scoped> とすると、そのスタイルはそのコンポーネントに閉じた存在になります(他に影響を及ぼしません)。

SCSSファイルを追加する

一方、グローバルなものは、どのコンポーネント内にも置かず純粋なCSSファイルとして扱います。ファイルを作成していきますが、scss を扱うため、loader をインスールします。

npm install --save-dev node-sass sass-loader

下記ファイルを、それぞれ作成してください。

assets/scss/style.scss

@charset "utf-8";

@import '_variables';
@import '_base';

assets/scss/_variables.scss

$color-black: #333;
$color-lightgray: #eee;

assets/scss/_base.scss

body {
  color: $color-black;
  background-color: $color-lightgray;
  font-size: 14px;
  letter-spacing: 0.05em;
}

これで準備が整ったので、nuxt.config.js から、そのファイルをプロジェクトに適用します。また、 reset-css などはお好みですが、このタイミングでインストールします。

npm install --save-dev reset-css

CSSファイルを読み込む

nuxt.config.js 内に、下記のように追記してください。作成した scss ファイルと、reset-cssをグローバルに登録しています。

/*
  ** Global CSS
  */
  css: [
		'reset-css', // 上記でインスールしていれば
		'~/assets/scss/style.scss'
  ],

~ の扱いは、画像パスやコンポーネントの読み込みでも同じですね。

ここで、かなり見た目が変わったのではないでしょうか。

修正・追加・削除等があったページ

  • [修正] nuxt.config.js
  • [追加] assets/scss/style.scss
  • [追加] assets/scss/_variables.scss
  • [追加] assets/scss/_base.scss

作成済みコンポーネントにスタイルを追加する

コンポーネント内でもSCSSを使うことができるので、属性は <style scoped lang="scss"> とします。行数あるので、開閉してコピーしてください(「▶︎」マークをクリックで開閉)。

 

assets/scss/_variables.scss
$color-black: #333;
$color-gray: #aaa;
$color-lightgray: #eee;
$color-blue: #3581FF;
pages/index.vue
<style scoped lang="scss">
  @import "~assets/scss/variables";

  .about,
  .works {
    h2 {
      display: flex;
      align-items: center;
      margin: 50px 0 30px;
      font-size: 30px;

      &::after {
        content: '';
        display: block;
        flex: 1;
        height: 1px;
        margin-left: 10px;
        background-color: $color-black;
      }
    }
  }

  .article-body {
    h3 {
      font-size: 20px;
      margin: 30px 0 20px;
    }

    p {
      line-height: 1.75;

      + p {
        margin-top: 20px;
      }

      a {
        color: $color-blue;
      }
    }

    ul {
      list-style: circle;
      margin-top: 20px;
      margin-left: 20px;

      li {
        &:nth-of-type(n+2) {
          margin-top: 0.5em;
        }
      }
    }

    img {
      max-width: 100%;
    }
  }
</style>
pages/about/index.vue
<style scoped lang="scss">
  @import "~assets/scss/variables";

  .about,
  .history {
    h2 {
      display: flex;
      align-items: center;
      margin: 50px 0 30px;
      font-size: 30px;

      &::after {
        content: '';
        display: block;
        flex: 1;
        height: 1px;
        margin-left: 10px;
        background-color: $color-black;
      }
    }
  }

  .article-body {
    h3 {
      font-size: 20px;
      margin: 30px 0 20px;
    }

    p {
      line-height: 1.75;

      + p {
        margin-top: 20px;
      }

      a {
        color: $color-blue;
      }
    }

    ul {
      list-style: circle;
      margin-top: 20px;
      margin-left: 20px;

      li {
        &:nth-of-type(n+2) {
          margin-top: 0.5em;
        }
      }
    }

    img {
      max-width: 100%;
    }
  }
</style>
layouts/default.vue
<style scoped lang="scss">
  .contents {
    max-width: 500px;
    padding: 40px 10px;
    margin: 0 auto;
  }
</style>
layouts/error.vue
<style scoped lang="scss">
  @import "~assets/scss/variables";

  .error {
    h2 {
      display: flex;
      align-items: center;
      margin: 50px 0 30px;
      font-size: 30px;

      &::after {
        content: '';
        display: block;
        flex: 1;
        height: 1px;
        margin-left: 10px;
        background-color: $color-black;
      }
    }
  }

  .article-body {
    h3 {
      font-size: 20px;
      margin: 30px 0 20px;
    }

    p {
      line-height: 1.75;

      + p {
        margin-top: 20px;
      }

      a {
        color: $color-blue;
      }
    }

    ul {
      list-style: circle;
      margin-top: 20px;
      margin-left: 20px;

      li {
        &:nth-of-type(n+2) {
          margin-top: 0.5em;
        }
      }
    }

    img {
      max-width: 100%;
    }
  }
</style>
components/common/TheHeader.vue
<style scoped lang="scss">
  @import "~assets/scss/variables";

  .header {
    background-color: $color-gray;
  }

  .header-inner {
    height: 60px;
    max-width: 600px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;

    a {
      color: inherit;
    }
  }
</style>

 

components/common/TheFooter.vue
<style scoped lang="scss">
  @import "~assets/scss/variables";

  .footer {
    background-color: $color-gray;
    text-align: center;
    line-height: 30px;
  }
</style>

 

そのまま用いれば、こんな感じの見た目になります。

とはいえ、<style scoped> の恩恵で、他コンポーネントに影響を及ぼさないとなると、同じ記述を繰り返す必要があり、かなり冗長になってしまいました。

そのため、共通部分は共通パーツとしてコンポーネントを切り出すか、グローバルに登録しておくか、を検討しましょう。

実際にコンポーネントを切り出す作業は次回にします。

修正・追加・削除等があったページ

  • [修正] assets/scss/_variables.scss
  • [修正] layouts/default.vue
  • [修正] layouts/error.vue
  • [修正] pages/about/index.vue
  • [修正] pages/index.vue
  • [修正] components/common/TheFooter.vue
  • [修正] components/common/TheHeader.vue

おわりに

ブラウザでの見た目はだんだんそれっぽくなってきました。同時に、コンポーネントの扱いにも慣れてきましたか?

次回はさらにコンポーネントとNuxtを活用して、便利&快適にコーディングを進めていきたいと思います! それでは!