Client-side setup

Build a Vue app using Typescript with the @vite tool.

Install dependencies

cd static/vue
npm create vite@latest . -- --template vue-ts

Install the Inertia dependendencies:

npm install --save @inertiajs/inertia @inertiajs/vue3

Reconfigure the app deleting all the auto generated files we won’t need and creating missing folders:

rm -rfv src/App.vue src/components/ src/assets public/ index.html README.md index.html
mkdir src/pages/

Vue configuration

There are modification to the Vue configuration to make it usable with our application:

By default, Vue embed a vite dev server to serve the app. It will be disabled in the package.json file replacing it with a build development mode. This task will allow you to configure a hot-reload development environment generating the main.js file. This file will then be served by the Flask app.

   "scripts": {
-    "dev": "vite",
+    "build:dev": "vite build --mode=development --watch",
+    "build:prod": "vite build",
     "build": "vue-tsc -b && vite build",
     "preview": "vite preview"
   }

Vue needs to be configured to generate the JavaScript code into the static/dist as configured in the server-side application. Based on the application architecture, there will be no need to generate a html file with Vue since our base.html will be rendered by Flask. Those configuration are stored in a vite.config.js file in the static/vue folder.

vite.config.js
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig(({ command, mode, ssrBuild }) => {
  const config = {
    plugins: [vue()],
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      }
    },
    base: '/dist'
  };
  const buildConfig = {
    outDir: '../dist/',
    emptyOutDir: true,
    rollupOptions: {
      input: 'src/main.ts',
      output: {
        entryFileNames: '[name].js',
        assetFileNames: 'assets/[name].[ext]'
      }
    }
  };

  return {
    ...config,
    build: buildConfig
  }
});

Integrate Inertia

Modify the static/vue/src/main.ts file as followed:

main.ts
import { createApp, h } from 'vue'
import type { App } from 'vue'
import { createInertiaApp } from '@inertiajs/vue3'


type StrOrNum = string | number

declare global {
  interface Window {
    reverseUrl(
      urlName: string, args?: Record<string, unknown> | StrOrNum | StrOrNum[]
    ): string
  }
}

// create a plugin to use window.reverseUrl in our Components
const routePlugin = {
  install: (app: App, _options: Record<string, unknown>) => {
    app.config.globalProperties.$route = window.reverseUrl
  }
}

createInertiaApp({
  resolve: name => {
    const pages = import.meta.glob('./pages/**/*.vue', { eager: true })
    return pages[`./pages/${name}.vue`]
  },
  setup({ el, App, props, plugin }) {
    const vueApp = createApp({ render: () => h(App, props) })
    vueApp.use(plugin)
    vueApp.use(routePlugin)
    vueApp.mount(el)
  }
})

In order to tell TypeScript about this new property $route, we are going to use module augmentation as mentioned in Vue 3 documentation.

Create a new route-plugin.d.ts which will be used by TypeScript to determine components’ global methods:

route-plugin.d.ts
import { Inertia } from '@inertiajs/inertia'

type StrOrNum = string | number

declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $route: (urlName: string, args?: Record<string, unknown> | StrOrNum | StrOrNum[]): string
    $inertia: typeof Inertia
  }
}

Create your views

In the Server-side setup chapter we created two views: index and params. These views use respectively a Index.vue and a Params.vue file stored in the static/vue/src/pages folder. It can be implemented as followed:

Index.vue
<script lang="ts" setup>
  import { PropType } from 'vue'

  defineProps({
   foo: {
     type: String as PropType<string>,
     required: true
   },
   fiz: {
     type: String as PropType<string>,
     required: true
   },
   num: {
     type: Number as PropType<number>,
     required: true
   },
  })
</script>

<template>
  <div class="content">
    <p class="field">
      <span class="label">Foo :</span>
      <span class="value">{{ foo }}</span>
    </p>
    <p class="field">
      <span class="label">Fiz :</span>
      <span class="value">{{ fiz }}</span>
    </p>
    <p class="field">
      <span class="label">Num :</span>
      <span class="value">{{ num }}</span>
    </p>
  </div>
</template>
Params.vue
<template>
  <strong>It works</strong>
</template>

For more options creating your views, please read the provided Inertia documentation.