Nuxt + Typescriptでdevice毎にlayoutを分ける

# はじめに
Nuxt + Typescriptでアプリケーションを開発している際、device毎（PC, スマホ）で表示するlayoutをそれぞれ分けて作成・表示したかったため、そのために行った内容をまとめます。

# 環境
- Nuxt.js 2.8.1
- Node.js 8.11.4
- npm 6.4.0

# ディレクトリ構成
```
├── .nuxt
├── node_modules
├── src
│   ├── layouts
│   │   ├── pc
│   │   │   └── default.vue
│   │   └── sp
│   │       └── default.vue
│   ├── pages
│   │   ├── pc
│   │   │   └── index.vue
│   │   └── sp
│   │       └── index.vue
│   ├── server
│   │   └── index.ts
│   └── router.ts
├── nuxt.config.ts
├── package-lock.json
└── package.json
```
※関連するディレクトリ・ファイルのみ表示

# routerをカスタマイズ
Nuxtのrouterをカスタマイズするにあたって```nuxt-community/router-module```というモジュールを利用します。
https://github.com/nuxt-community/router-module
このモジュールは独自のrouterを実装することで、Nuxtアプリケーションのroutingをカスタマイズすることを可能とします。
> Use your own router.js to handle your routes into your Nuxt.js application.

### router-moduleのインストール
以下のコマンドにてモジュールをインストール。

```bash
$ npm install --save @nuxtjs/router
```

### router.tsを作成
インストールが完了したらsrcDirに独自のrouterを作成します。
今回はTypescriptでアプリケーションの開発を行っているため、`router.ts`を作成しました。

```typescript:src/router.ts
/* eslint @typescript-eslint/no-explicit-any: 0 */
import Vue from 'vue';
import Router, { RouteConfig } from 'vue-router';
import { createRouter as createDefaultRouter } from '~~/.nuxt/defaultRouter.js';

Vue.use(Router);

const fixRoutes = (
  defaultRoutes: RouteConfig[],
  isMobile: boolean,
): RouteConfig[] => {
  const regex = isMobile ? /^\/sp\/?/ : /^\/pc\/?/;

  return defaultRoutes
    .filter((route: RouteConfig): boolean => regex.test(route.path))
    .map(
      (route: RouteConfig): RouteConfig => ({
        ...route,
        path: route.path.replace(regex, '/'),
      }),
    );
};

export const createRouter = (ssrContext: Record<string, any>): Router => {
  const defaultRouter: Record<string, any> = createDefaultRouter();
  // TODO：mobile判定は後で実装するため、一旦固定でmobile判定
  const isMobile = true;

  return new Router({
    ...defaultRouter.options,
    routes: fixRoutes(defaultRouter.options.routes, isMobile),
  });
};
```
`router.ts`ではNuxtで生成された`defaultRouter.js`を再利用することでカスタマイズしております。（`defaultRouter.js`の生成方法については後述）
実装した`fixRoutes`関数では、引数として渡されたmobile判定情報を元に`sp`もしくは`pc`ディレクトリのどちらかをroutingのpathとして採用し、defaultのroute情報をベースとして新たなrouteを生成します。
それにより、device毎それぞれ別々のroutingが生成され、PCでアクセスした際は`/pages/pc/`を、スマホでアクセスした際は`/pages/sp/`を表示することが可能となります。

また、device判定の実装については後述とし、ここでは一旦固定で定義しています。

### modulesの設定を追加
`router.ts`を作成したら`nuxt.config.ts`のmodulesに`@nuxtjs/router`の設定を追加します。

```typescript:nuxt.config.ts
// ・・・省略
const nuxtConfig: NuxtConfiguration = {
  mode: 'universal',
  srcDir: 'src/',
  // ・・・省略
  modules: [
    ['@nuxtjs/router', { fileName: 'router.ts', keepDefaultRouter: true }],
  ],
  // ・・・省略
```
optionsとしては`path`と`fileName`、`keepDefaultRouter`の指定が可能です。
`fileName`に関してはdefault値が`router.js`なため、今回はtsファイル（`router.ts`）を指定。
`keepDefaultRouter`はtrueを設定すると`.nuxt`ディレクトリに`defaultRouter.js`としてNuxtのデフォルトrouterが保持され、独自に作成したrouterからアクセスし再利用することが可能となります。

# deviceの判定
routerのカスタマイズまで完了したら、次にdeviceの判定処理を実装します。
deviceの判定には`hgoebl/mobile-detect.js`というライブラリを利用しました。
https://github.com/hgoebl/mobile-detect.js

このライブラリはUserAgentをもとに、deviceの判定を行います。

### mobile-detect.jsのインストール
```bash
$ npm install --save mobile-detect
```

### deviceの判定処理実装
インストールが完了したら最後にTODOとしていたdeviceの判定処理を実装します。

```typescript:src/router.ts
// ・・・省略
import MobileDetect from 'mobile-detect';

Vue.use(Router);

const isMobile = (ssrContext: Record<string, any>): boolean => {
  const ua = ssrContext
    ? ssrContext.req.headers['user-agent']
    : window.navigator.userAgent;

  const md: MobileDetect = new MobileDetect(ua);

  return !!md.mobile();
};

// ・・・省略

export const createRouter = (ssrContext: Record<string, any>): Router => {
  const defaultRouter: Record<string, any> = createDefaultRouter();

  return new Router({
    ...defaultRouter.options,
    routes: fixRoutes(defaultRouter.options.routes, isMobile(ssrContext)),
  });
};
```

`isMobile`関数を実装し、固定でtrueを定義していた`isMobile`変数と置き換えます。
`isMobile`関数の中では`mobile-detect`ライブラリを用いたmobile判定が行われており、`MobileDetect`インスタンス生成の際にUserAgentを渡すことで判定が可能になります。（mobile判定の他にもtabletやos、iPhoneであるかどうかなど様々な判定が可能）

以上によりUserAgentの情報を元にdevice毎それぞれlayoutを分けることが可能となりました。

# 参考にしたサイト
- [Nuxt のルーティングを PC とスマートフォンで切り替える](https://ymmooot.netlify.com/articles/3/)
- [nuxtでPCとSPの切り分け](https://forum.vuejs.org/t/nuxt-pc-sp/34599)