くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

最近のNuxtで多言語対応のLP/ドキュメントサイトを作ってみた(content/i18n/SEO)

ほそぼそと作ってるマグロのタッチゲーム、
いちようLPを作ってみた(*´ω`*)

maguro-sagashi.com

普段、Nuxt.jsでWebアプリとかを作るけど、
最近のモジュールをいろいろ試してみたときの備忘録(*´ω`*)

使ったモジュール/構成

構成はこんな感じ。

  • 記事関連: content/i18n
  • UI系: nuxt-ui(Tailwind)/nuxt-fonts
  • SEO系: Nuxt SEO/nuxt-gtm
  • シェア: nuxt-social-share

内包しているのを合わせるとこんな感じ。

はじめは@nuxt/imageも使ってけど、
使うほどでもない&バンドルサイズを下げたいので、使わない形に。

Nuxt Content

まずはテンプレートで作成

$ pnpm dlx nuxi init my-docs -t content

Document Drivenモードもあるけど、
多くの問題があり、次のバージョンでなくなるらしい。。

Nuxt UI

UIコンポーネントなどは、Nuxt UIを利用
TailwindやIconifyなどで構成されているので便利

$ pnpm add -D @nuxt/ui 
# material iconも使いたいので、追加
$ pnpm add -D @iconify-json/mdi

nuxt.config.tsの設定

nuxt.config.tsに設定を追加しておく

// nuxt.config.ts
export default defineNuxtConfig({
  static: true,
  modules: [
    '@nuxt/content',
    '@nuxt/ui',
  ],

  // ********************************************************
  // * @nuxt/content: https://content.nuxt.com/
  // ********************************************************
  content: {
  },
  
  // ********************************************************
  // * @nuxt/ui: https://ui.nuxt.com/
  // ********************************************************
  ui: {
    // .mdからコンポーネントが使えるようにglobal importにする
    global: true,
    // 利用するiconの種類を指定
    icons: ['mdi', "heroicons"],
  },
  
  // ********************************************************
  // * @nuxt/color-mode: https://color-mode.nuxtjs.org/
  // ********************************************************
  colorMode: {
    preference: "light",
  },
});

tailwind.configの設定

content/**/*.mdでもtailwindcssの対象になるように設定

// tailwind.config.ts
import type { Config } from 'tailwindcss';

export default <Partial<Config>>{
  content: [
    'content/**/*.md'
  ]
};

VSCodeの設定

Tailwindでの補完に関するVSCodeの設定を追加

// .vscode/settings.json
{
  "files.associations": {
      "*.css": "tailwindcss"
  },
  "editor.quickSuggestions": {
      "strings": true
  },
  "tailwindCSS.experimental.configFile": "tailwind.config.ts",
  // or
  "tailwindCSS.experimental.configFile": {
    "my-docs/tailwind.config.ts": "my-docs/**"
  }
}

Nuxt Fonts/Google Fonts

こんな感じで、フォント名(Roboto)を書くだけで、
assetsにダウンロードしてくれるモジュール

<template>
  <div>
    Hello Nuxt Fonts!
  </div>
</template>

<style scoped>
div {
  font-family: Roboto, sans-serif;
}
</style>
$ pnpm add -D @nuxt/fonts
// nuxt.config.ts
export default defineNuxtConfig({
  static: true,
  modules: [
    '@nuxt/content',
    '@nuxt/ui',
    "@nuxt/fonts",
  ],

  // 略  
});

TailwindCSSを使っていても、認識してくれるらしい

Nuxt Fontsはassetsにダウンロードして、
自分のサイトから配信する形

自身では配信せず、もとのGoogle FontsのURLから取得したい場合は、
@nuxtjs/google-fontsのほうがよいかも

@nuxtjs/google-fontsの場合は、こんな感じ

// nuxt.config.ts
export default defineNuxtConfig({
  static: true,
  modules: [
    // 略
    '@nuxtjs/google-fonts',
  ],

  // 略  
  
  // ********************************************************
  // * @nuxtjs/google-fonts: https://google-fonts.nuxtjs.org
  // ********************************************************
  googleFonts: {
    families: {
      "Kaisei Tokumin": true,
    }
  },
});

Nuxt i18n

英語版も用意したいので、nuxt-i18nも導入

$ pnpm add -D @nuxtjs/i18n

設定はこんな感じ

// nuxt.config.ts
export default defineNuxtConfig({
  static: true,
  modules: [
    // 略
    '@nuxtjs/i18n',
  ],
  // 略
  
  // ********************************************************
  // * @nuxtjs/i18n: https://i18n.nuxtjs.org
  // ********************************************************
  i18n: {
    // デフォルトの言語
    defaultLocale: "ja",
    // パスに言語を含めるかどうか。デフォルト以外は含める設定
    strategy: "prefix_except_default",
    baseUrl: "<BASE_URL>",
    // ブラウザの言語を使って切り替えるかどうか
    detectBrowserLanguage: false,
    // 利用できる言語の一覧
    locales: [
      { code: "ja", iso: "ja", name: "日本語" },
      { code: "en", iso: "en", name: "English" },
    ],
  },
});

あとは、多言語ファイルを用意。

export default defineI18nConfig(() => ({
  legacy: false,
  messages: {
    en: {
      title: "MAGURO Finding",
      desc: "A simple casual game to find tuna among mackerel",
    },
    ja: {
      title: "マグロ探し",
      desc: "鯖(サバ)の中から鮪(マグロ)を探す無料のシンプルカジュアルゲーム",
    },
  }
}));

手前味噌だけど、SSSAPIを使うと、
スプレットシートからいい感じのJSONを取得できるので、そのまま使ってる

https://sssapi.app/help/sample4

// curlとかで、SSSAPIからi18n.jsonを取得&保存
import i18Json from "./i18.json";

export default defineI18nConfig(() => ({
  legacy: false,
  messages: i18Json
}));

Nuxt SEO(@nuxtjs/seo)

SEO系はsitemapとrobots.txtだけ対応

基本的にURLだけ指定すれば、Nuxt SEO
i18nも考慮したパスでsitemapとかを作ってくれる

// nuxt.config.ts
export default defineNuxtConfig({
  static: true,
  modules: [
    // 略
    'nuxt-simple-robots',
    '@nuxtjs/sitemap',
  ],
  // 略
  
  // ********************************************************
  // * Nuxt SEO: https://nuxtseo.com/
  // ********************************************************
  // サイト全体の設定
  site: {
    url: "<BASE_URL>",
  },
  // robots.txtの設定
  robots: {
    disallow: [],
  },
  // sitemapの設定
  sitemap: {
  },
});

ページごとのtitleとかは、Nuxt標準のuseHead<Head>を使って設定する感じ

動的ページでOGPを生成したい場合は、
Nuxt OG Imageを使えばOK。
Vueコンポーネントなどを画像化してくれる。

@nuxt/gtm

Google Tag Managerを使いたいので、
Nuxt-GTMを導入

// nuxt.config.ts
export default defineNuxtConfig({
  static: true,
  modules: [
    // 略
    '@zadigetvoltaire/nuxt-gtm',
  ],
  // 略
  
  // ********************************************************
  // * @zadigetvoltaire/nuxt-gtm: https://github.com/zadigetvoltaire/nuxt-gtm
  // ********************************************************
  gtm: {
    id: "GTM-XXXXXXXX",
  },
});

これだけでOK。vue-gtmのラッパーモジュールなので、
細かいイベント送信とかは、こちらも合わせて読みつつ

Google Analytics 4の場合は、Nuxt Gtagを使えばOK

export default defineNuxtConfig({
  modules: ['nuxt-gtag'],

  gtag: {
    id: 'G-XXXXXXXXXX'
  }
})

シェア機能

シェア機能もつけたいなと思ったら、便利なライブラリがあったので導入

これだけで、いまのURLをシェアできるように(*´ω`*)
SSGの場合は<ClientOnly>で囲むか、urlパラメタを指定する必要がある。

<template>
  <ClientOnly>
    <SocialShare network="twitter" :styled="true" :label="true" class="text-white" />
  </ClientOnly>
<template>

小ネタ

i18nでのmetaタグはapp.vueを使う

i18n側でhead.metaなどを用意してくれてる。
各言語用のOGPやtitleなどを設定したい場合は、
app.vueに書いておくのがよい感じ。

<template>
  <Html :lang="head.htmlAttrs.lang" :dir="head.htmlAttrs.dir">

  <Head>
    <template v-for="meta in head.meta" :key="meta.id">
      <Meta :hid="meta.property" :name="meta.property" :property="meta.property" :content="meta.content" />
    </template>

    <template v-for="link in head.link" :key="link.id">
      <Link :id="link.id" :rel="link.rel" :href="link.href" :hreflang="link.hreflang" />
    </template>
  </Head>

  <Body>
    <NuxtLayout>
      <NuxtPage />
    </NuxtLayout>
  </Body>

  </Html>
</template>

<script setup lang="ts">
const { t, locale } = useI18n();
const baseUrl = useRuntimeConfig().public.baseUrl;
const head = useLocaleHead({
  addDirAttribute: true,
  identifierAttribute: 'id',
  addSeoAttributes: true,
});

const ogImageFileName = computed(() => {
  if (locale.value == "ja") return `${baseUrl}/ogp.png`;
  else return `${baseUrl}/ogp_en.png`;
});

useSeoMeta({
  ogType: "website",
  title: t("title"),
  ogTitle: t("title"),
  description: t("desc"),
  ogDescription: t("desc"),
  ogImage: ogImageFileName,
});
</script>

i18nとcontentの使い分け

単語や文ならnuxt-i18nを使い、
長い文書とかならnuxt-contentを使うのがいいらしい

そのまま、ディレクトリをわけて記事全体を用意する形でもいいし、

content/
  en/
    index.md
  index.md

こんなコンポーネントを作っておくのでもOK

<!-- components/PartsArticle.vue -->
<template>
  <div class="text-white font-bold prose prose-sm sm:prose-lg">
    <ContentDoc :path="mdPath" :head="false" />
  </div>
</template>

<script setup lang="ts">
const { locale } = useI18n();

const mdPath = computed(() => {
  if (locale.value == "ja") return `/_parts_article`;
  else return `/en/_parts_article`;
});
</script>

SSGではdetectBrowserLanguageはOFFがいいかも?

ブラウザの言語を検出して、リダイレクトしてくれる機能があるけど、
SSGの場合は、うまく言語が切り替わらないなどあり、OFFにするようにした。

設定がよくない気がするので、時間に余裕があるときに再チャレンジしたい。。

デフォルト言語だけのページは、pages/に用意する

表示している言語で遷移先のパスを指定するとき、
nuxt-i18nではlocalePathを使う。

<template>
  <nuxt-link :to="localePath('/')">
    {{ t("app.title") }}
  </nuxt-link>
</template>

<script lang="ts" setup>
const localePath = useLocalePath();
const { t } = useI18n();
</script>

デフォルトの言語しか用意していないページの場合、
同じページを複製するのも大変なので、
pages/にパスにあったvueを配置して切り替えないようにした。

うまくできる方法があるといいけど、また時間に余裕があるときに。。


以上!! 公式モジュールだけで、簡単にLP/ドキュメントサイトが作れるのはいいね(´ω`)
markdownを追加だけで、ヘルプとかお知らせとかも追加できる!!

作ってるゲームも事前予約受付中なので、
こちらもよろしくおねがいします〜(*´ω`*)

maguro-sagashi.com