Viteを使ってSSGを試した話

Viteは高速ビルドツールですが、様々なプラグインが開発されており、その中にはVueを静的なHTMLへ出力してくれる（つまり、SSGしてくれる）ものがあったので、試してみました。

https://ja.vitejs.dev/

プラグインは、 `vite-ssg` を使います。
ドキュメント記載の通り、インストールして、セットアップします。

https://github.com/antfu/vite-ssg

注意すべき点としては、createAppではなく、ViteSSGを使うこと。

```ts
// src/main.ts
import { ViteSSG } from 'vite-ssg'
import App from './App.vue'

+ // `export const createApp` is required instead of the original `createApp(App).mount('#app')`
+ export const createApp = ViteSSG(
+  // the root component
+  App,
+  // vue-router options
+  { routes },
+  // function to have custom setups
+  ({ app, router, routes, isClient, initialState }) => {
+    // install plugins etc.
+  }
+ )
```

また、 `vue-ssg` のドキュメントで、 `vue-router` をインストールしていることから、ルーティングは`vue-router`を使います。
`createRouter()` して、ルーティング情報を書いても良いのですが、ここでも `vite-plugin-pages`というプラグインを使うと便利でした。

https://github.com/hannoeru/vite-plugin-pages

これをインストールことで、 `src/pages` 配下にvueファイルを配置すれば、よしなにルーティングしてくれます。
`vite.config.ts` に `Pages()` を追加します。

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'

export default defineConfig({
  plugins: [
    vue(),
+   Pages(),
  ]
})
```

TypeScriptの場合はtypesも追加しておきましょう。

```tsx
// tsconfig.json
{
  "compilerOptions": {
+	"types": [
+     "vite/client",
+     "vite-plugin-pages/client",
+   ]
}
```

あとは、 `src/main.ts`に `virtual:generated-pages`を使ってよしなにルーティング情報からHTMLを生成してくれます。

```tsx
// src/main.ts
import { ViteSSG } from 'vite-ssg'
import generatedRoutes from 'virtual:generated-pages'
import App from './App.vue'

+ const routes = generatedRoutes

export const createApp = ViteSSG(
  // the root component
  App,
  // vue-router options
  { routes },
  // function to have custom setups
  ({ app, router, routes, isClient, initialState }) => {
    // install plugins etc.
  }
)
```

```
vite-ssg build
```

![package_json_—_workspace__ワークスペース_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/bedc6347-c201-bbb1-9f62-583ee4b428b8.png)

これで問題ないと思いきや・・・

生成したHTMLは、`vue-router` を使ってルーティングするため、SSGで静的HTMLを生成しても、直接参照することができません。
たとえば、 `localhost:8100`から `router-link` を使って、`localhost:8100/about`や`localhost:8100/about/`への遷移は可能ですが、`localhost:8100/about`や`localhost:8100/about/`を直接参照しようとすると「404 not found」が返ってきます。

![vite-ssg-local.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/41e175e7-2881-df89-3005-6b6031f24405.gif)

SSGすると、つくられるのは`about.html`なので、`localhost:8100/about`だと「404 not found」が発生してしまうのは当然です。

では、どうすれば良いのか？:thinking:

apatchやnginx等だと、configファイルなどにURLのパスに対して`.html`拡張子付きのファイルを参照するように設定を書くことで回避できます。
しかし、AWS S3などの単純なストレージに対してはそうはいきません。

方法はいくつかありますが、SSGの出力を見直すのが良さそうです。
`localhost:8100/about/`は、`localhost:8100/about/index.html`のことなので、このようにHTMLを出力するように `vite-ssg` 側を書き換えます。

```ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Pages from 'vite-plugin-pages'

export default defineConfig({
  plugins: [
    vue(),
    Pages({
+      extendRoute: (route, parent) => {
+        if (!process.env['VITE_SSG']) return route
+
+        if (route.component.match(/\/index\.(vue|js)$/) && route.path !== '/') {
+          return {
+            ...route,
+            path: `${route.path}/index`
+          }
+        }
+        return route
+      }
+    }),
  ]
})
```
今回は、`vite-plugin-pages`の`extendRoute`に書き換え処理を書きましたが、`vite-ssg`の`ssgOptions`でも同様のことができたので、どちらでも良いと思います。（違いなどがあれば教えてください。）

![vite_config_ts_—_workspace__ワークスペース_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/a4590358-0c4f-cfa6-82ca-d2d940b008fe.png)

これで `about.html` ではなく、 `about/index.html` が出力されたことがわかります。
AWS S3だと、`localhost:8100/about`も`localhost:8100/about/`へリダイレクトしてくれるので、これで無事解決です。


## さいごに

Vue3でSSGを考えた時、Nuxt3が一番最初に思いつきますが、Nuxt3は現在SSGに未対応なようです。

https://v3.nuxtjs.org/getting-started/introduction/

![Nuxt_3_-_Introduction.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241956/3ebe8605-d1ff-7249-6b36-10a2858adce2.png)

なので、今現在、Vue3でSSGするなら`vite-ssg`を使うしかないのですが、思いのほか色々なところで躓いたので、それを記録として残しました。`vite-ssg` で躓いている方々への参考になれば幸いです。