0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Nuxt 入門:はじめての一歩

0
Last updated at Posted at 2026-04-30

はじめに

Nuxtは、Vue.jsをベースにしたフルスタックフレームワークです。
Vue.jsの使いやすさはそのままに、ファイルベースのルーティングサーバーサイドレンダリング(SSR)自動インポートなど、本格的なWebアプリケーション開発に必要な機能が最初から揃っています。

この記事では、カウンターアプリを題材にしながら、Nuxtの基本的な考え方と使い方を丁寧に解説します。


1. Nuxtとは

NuxtはVue.jsアプリケーションの開発をより快適にするためのフレームワークです。
「設定より規約(Convention over Configuration)」の思想に基づいており、ファイルを決まった場所に置くだけで多くの機能が自動的に有効になります。

NuxtとVue.jsの違い

機能 Vue.js単体 Nuxt
ルーティング 手動で設定 ファイル構成から自動生成
SSR/SSG 自分でセットアップ 設定一つで切り替え可能
importの記述 毎回書く必要あり 自動インポートで省略可能
メタタグ管理 別ライブラリが必要 useHead で簡単に設定
サーバーAPI 別途用意が必要 server/api/ に置くだけ

レンダリングモード

Nuxtは用途に合わせてレンダリング方式を選べます。

モード 説明 向いている用途
SSR サーバーでHTMLを生成 ECサイト・ニュースサイト
SSG ビルド時にHTMLを生成 ブログ・ドキュメントサイト
SPA クライアントで描画 管理画面・社内ツール

2. 環境セットアップ

Node.js(v18以上推奨)がインストールされていることを確認してください。

node -v

プロジェクトの作成

nuxi コマンドを使って新しいプロジェクトを作成します。

npx nuxi@latest init my-counter-app
cd my-counter-app
npm install
npm run dev

ブラウザで http://localhost:3000 を開くと、Nuxtアプリが起動しています。


3. プロジェクト構成を理解する

Nuxtプロジェクトには、役割ごとに決まったディレクトリがあります。
それぞれのディレクトリを正しく使うことで、Nuxtの機能を最大限に活用できます。

my-counter-app/
├── pages/           # ページコンポーネント(URLと対応)
├── components/      # 再利用可能なUIコンポーネント
├── composables/     # 再利用可能なロジック(useXxx)
├── server/
│   └── api/         # サーバーサイドAPI
├── public/          # 静的ファイル(画像など)
├── assets/          # CSSや画像(ビルド対象)
├── app.vue          # アプリ全体のルートコンポーネント
└── nuxt.config.ts   # Nuxtの設定ファイル

ポイント: これらのディレクトリは存在しなければ機能しません
必要になったタイミングで自分で作成してください。


4. ページとルーティング

Nuxtでは pages/ ディレクトリにファイルを作るだけで、URLが自動的に生成されます。
Vue Routerの設定を手動で書く必要はありません。

ファイルとURLの対応

pages/
├── index.vue          → /
├── about.vue          → /about
└── counter/
    └── index.vue      → /counter

pages/index.vue の例

<template>
  <div>
    <h1>トップページ</h1>
    <!-- NuxtLink でページ間を移動します -->
    <NuxtLink to="/counter">カウンターへ</NuxtLink>
  </div>
</template>

<NuxtLink> はVue Routerの <RouterLink> に相当するNuxt組み込みのコンポーネントです。
通常の <a> タグと異なり、ページ全体をリロードせずに移動できます。

動的ルート(パラメーター付きURL)

ファイル名を [id].vue のように角括弧で囲むと、動的なURLを作れます。

pages/
└── counter/
    └── [id].vue       → /counter/1, /counter/abc など
<script setup>
// URLのパラメーターを取得
const route = useRoute();
const id = route.params.id;
</script>

<template>
  <p>カウンターID:{{ id }}</p>
</template>

5. コンポーネントを作る

components/ ディレクトリに .vue ファイルを作成すると、自動的にインポートされ、どのページからでもすぐに使えます。

components/CountDisplay.vue

<script setup>
// propsの定義
const props = defineProps({
  count: {
    type: Number,
    required: true,
  },
});

// countの値に応じて色を返す
const color = computed(() => {
  if (props.count > 0) return "#22c55e";
  if (props.count < 0) return "#ef4444";
  return "#333";
});
</script>

<template>
  <p class="count" :style="{ color }">
    {{ count }}
  </p>
</template>

<style scoped>
.count {
  font-size: 5rem;
  font-weight: bold;
  text-align: center;
  transition: color 0.3s;
}
</style>

ページで使う

<!-- pages/counter/index.vue -->
<template>
  <div>
    <!-- importなしでそのまま使えます -->
    <CountDisplay :count="42" />
  </div>
</template>

サブディレクトリのコンポーネント

サブディレクトリに配置したコンポーネントは、ディレクトリ名がプレフィックスになります。

components/
└── Counter/
    ├── Display.vue    → <CounterDisplay />
    └── Buttons.vue    → <CounterButtons />

6. 自動インポートを理解する

Nuxtの大きな特徴の一つが自動インポートです。
VueのAPIやcomposables、コンポーネントを import 文なしで使えます。

自動インポートされるもの

<script setup>
// 通常のVue.jsでは以下が必要ですが…
// import { ref, computed, watch } from 'vue'
// import { useRoute } from 'vue-router'

// Nuxtでは書かなくてもそのまま使えます
const count = ref(0);          // ✅ import不要
const route = useRoute();      // ✅ import不要
const doubled = computed(() => count.value * 2);  // ✅ import不要
</script>
自動インポートの対象
VueのComposition API ref, computed, watch, onMounted など
Vue Routerのcomposables useRoute, useRouter
Nuxt組み込みcomposables useState, useHead, useFetch など
composables/ のファイル 自分で作った useCounter など

7. reactiveとrefでstateを管理する

NuxtはVueがベースとなっているため、Vue.jsの refreactive を使ってリアクティブなデータを管理します。

ref:プリミティブ値に使う

文字列・数値・真偽値などの単純な値には ref を使います。

<script setup>
const count = ref(0);       // 数値
const name = ref("Nuxt");   // 文字列
const isVisible = ref(true); // 真偽値

// .value でアクセス・更新(template内では不要)
count.value++;
console.log(count.value); // 1
</script>

<template>
  <!-- template内では .value 不要 -->
  <p>{{ count }}</p>
</template>

reactive:オブジェクトに使う

複数の値をまとめて管理する場合は reactive が便利です。

<script setup>
const state = reactive({
  count: 0,
  step: 1,
  history: [],
});

// .value 不要でアクセスできます
state.count += state.step;
state.history.push(state.count);
</script>

<template>
  <p>カウント:{{ state.count }}(ステップ:{{ state.step }}</p>
</template>

8. はじめてのカウンター

学んだことを組み合わせて、シンプルなカウンターページを作ってみましょう。

<!-- pages/counter/index.vue -->
<script setup>
const count = ref(0);

function increment() {
  count.value++;
}

function decrement() {
  count.value--;
}

function reset() {
  count.value = 0;
}
</script>

<template>
  <div class="container">
    <h1>カウンター</h1>

    <!-- カウントの表示 -->
    <p
      class="count"
      :class="{
        positive: count > 0,
        negative: count < 0,
      }"
    >
      {{ count }}
    </p>

    <!-- ボタン群 -->
    <div class="buttons">
      <button @click="decrement"></button>
      <button @click="reset">リセット</button>
      <button @click="increment"></button>
    </div>
  </div>
</template>

<style scoped>
.container {
  text-align: center;
  padding: 2rem;
}

.count {
  font-size: 5rem;
  font-weight: bold;
  color: #333;
  transition: color 0.3s;
}

.count.positive { color: #22c55e; }
.count.negative { color: #ef4444; }

.buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
  margin-top: 1rem;
}

button {
  padding: 0.5rem 1.5rem;
  font-size: 1.2rem;
  cursor: pointer;
}
</style>

computed でメッセージを自動計算する

computed を使うと、stateの変化に応じて自動的に更新される値を定義できます。

<script setup>
const count = ref(0);

// countが変わるたびに自動的に再計算されます
const message = computed(() => {
  if (count.value > 10) return "10を超えました!";
  if (count.value < 0) return "マイナスです";
  return "通常範囲です";
});
</script>

<template>
  <p>{{ count }}{{ message }}</p>
</template>

9. useStateでstateを共有する

Vue.jsの ref は同じコンポーネント内でしか使えませんが、Nuxtの useState を使うと複数のコンポーネントやページ間でstateを共有できます。

<!-- components/Counter/Display.vue -->
<script setup>
// どのコンポーネントからも同じ "count" にアクセスできます
const count = useState("count", () => 0);
</script>

<template>
  <p class="count">{{ count }}</p>
</template>
<!-- components/Counter/Buttons.vue -->
<script setup>
// 同じキー "count" を指定すると同じstateが共有されます
const count = useState("count", () => 0);

function increment() { count.value++; }
function decrement() { count.value--; }
function reset()     { count.value = 0; }
</script>

<template>
  <div class="buttons">
    <button @click="decrement"></button>
    <button @click="reset">リセット</button>
    <button @click="increment"></button>
  </div>
</template>
<!-- pages/counter/index.vue -->
<template>
  <div class="container">
    <!-- どちらも同じstateを参照します -->
    <CounterDisplay />
    <CounterButtons />
  </div>
</template>

ポイント: useState の第一引数はキー名です。
同じキー名を指定すれば、異なるコンポーネントでも同じstateを共有できます。


10. composableに切り出す

カウンターのロジックが複雑になってきたら、composables/ に切り出して再利用しやすくしましょう。
composablesのファイル名は慣習として use で始めます。

composables/useCounter.ts

// composables/useCounter.ts
export function useCounter(initialValue = 0) {
  const count = useState("count", () => initialValue);
  const history = useState<number[]>("counter-history", () => []);

  function increment(step = 1) {
    count.value += step;
    history.value.push(count.value);
  }

  function decrement(step = 1) {
    count.value -= step;
    history.value.push(count.value);
  }

  function reset() {
    count.value = initialValue;
    history.value = [];
  }

  // countの絶対値(表示用)
  const absCount = computed(() => Math.abs(count.value));

  // カウントの状態
  const status = computed(() => {
    if (count.value > 0) return "positive";
    if (count.value < 0) return "negative";
    return "zero";
  });

  return {
    count: readonly(count),   // 外部から直接変更できないようにする
    history: readonly(history),
    absCount,
    status,
    increment,
    decrement,
    reset,
  };
}

コンポーネントで使う

<!-- pages/counter/index.vue -->
<script setup>
// importなしで使えます(自動インポート)
const { count, status, increment, decrement, reset } = useCounter(0);
</script>

<template>
  <div class="container">
    <p class="count" :class="status">{{ count }}</p>
    <div class="buttons">
      <button @click="decrement()"></button>
      <button @click="reset">リセット</button>
      <button @click="increment()"></button>
    </div>
  </div>
</template>

11. カウンターを完成させる

最終的なカウンターアプリのファイル構成と完全なコードをまとめます。

ファイル構成

my-counter-app/
├── composables/
│   └── useCounter.ts          # カウンターのロジック
├── components/
│   └── Counter/
│       ├── Display.vue        # 数値の表示
│       ├── Buttons.vue        # 操作ボタン
│       └── History.vue        # 操作履歴
└── pages/
    └── counter/
        └── index.vue          # カウンターページ

components/Counter/Display.vue

<script setup>
const { count, status } = useCounter();
</script>

<template>
  <p class="count" :class="status">{{ count }}</p>
</template>

<style scoped>
.count {
  font-size: 5rem;
  font-weight: bold;
  transition: color 0.3s;
}
.zero     { color: #6b7280; }
.positive { color: #22c55e; }
.negative { color: #ef4444; }
</style>

components/Counter/Buttons.vue

<script setup>
const { increment, decrement, reset } = useCounter();
</script>

<template>
  <div class="buttons">
    <button class="btn btn-minus" @click="decrement()"></button>
    <button class="btn btn-reset" @click="reset">リセット</button>
    <button class="btn btn-plus"  @click="increment()"></button>
  </div>
</template>

<style scoped>
.buttons {
  display: flex;
  gap: 1rem;
  justify-content: center;
}
.btn {
  width: 64px;
  height: 64px;
  font-size: 1.5rem;
  border: none;
  border-radius: 50%;
  cursor: pointer;
}
.btn-minus { background: #ef4444; color: #fff; }
.btn-plus  { background: #22c55e; color: #fff; }
.btn-reset { background: #6b7280; color: #fff; font-size: 0.9rem; }
</style>

components/Counter/History.vue

<script setup>
const { history } = useCounter();
</script>

<template>
  <div v-if="history.length" class="history">
    <h3>操作履歴</h3>
    <ul>
      <li v-for="(val, i) in history" :key="i">
        {{ i + 1 }}回目:{{ val }}
      </li>
    </ul>
  </div>
</template>

pages/counter/index.vue

<script setup>
// ページのメタ情報を設定します
useHead({
  title: "カウンター",
  meta: [{ name: "description", content: "Nuxt製カウンターアプリ" }],
});
</script>

<template>
  <div class="container">
    <CounterDisplay />
    <CounterButtons />
    <CounterHistory />
  </div>
</template>

<style scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  padding: 3rem 1rem;
  min-height: 100vh;
}
</style>

12. まとめ

この記事で学んだNuxtの基本をおさらいしましょう。

概念 役割
pages/ URLと対応するページ pages/counter/index.vue/counter
components/ 再利用可能なUI部品 <CounterDisplay />
composables/ 再利用可能なロジック useCounter()
自動インポート import 文を省略 ref, computed, 自作composable
useState ページをまたぐstate共有 useState("count", () => 0)
useHead ページのメタ情報設定 useHead({ title: "..." })

Vue.jsとNuxtのどちらを選ぶ?

要件 選択
シンプルなSPAを作りたい Vue.js単体で十分です
SEOが必要(ニュース・EC) Nuxt(SSR)がおすすめです
ビルド時に静的化したい Nuxt(SSG)がおすすめです
APIも一緒に管理したい Nuxt(server/api/)がおすすめです

次のステップ

  • useFetch / useAsyncData — 外部APIからのデータ取得
  • server/api/ — サーバーサイドAPIの作成
  • Nuxt Modules@nuxtjs/tailwindcss などのモジュール活用
  • Pinia — より本格的なグローバルstate管理
  • nuxt generate — 静的サイト生成(SSG)

参考リンク


この記事がNuxt学習の第一歩になれば幸いです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?