---
title: Upgrade Guide for Quasar CLI with Vite
---
> [!NOTE]
> This guide refers to upgrading a @quasar/app-vite v2 project to @quasar/app-vite v3.
> For older versions, please refer to [https://legacy-app.quasar.dev](https://legacy-app.quasar.dev).

## A note to App Extensions owners

You might want to release new versions of your Quasar App Extensions with support for the new `@quasar/app-vite`. If you are not touching the quasar.config configuration, then it will be as easy as just changing the following:

```js
api.compatibleWith(
  '@quasar/app-vite',
-   '^2.0.0'
+   '^3.0.0'
)
```

> [!WARNING]
> The changes to the engine behind App Extensions will only be compatible with @quasar/app-vite v3+ going forward. You will have to drop support for @quasar/app-vite v2 and @quasar/app-webpack (any version).

Removed `api.engine`, `api.hasVite`, `api.hasWebpack` and `api.hasLint`.

All `api.extendX(fn, api)` methods can now be async and optionally return a (Rolldown/etc) config that will be merged with the default one.

Example "api.extendX() example":

```js
api.extendSSRWebserverConf((rolldownConf, api) => {
  // add/remove/change Quasar CLI generated Rolldown config object

  // New! Now, optionally, we can also return a config object that will
  // be merged into the default one
  return {
    output: {
      banner: '/**! My Banner */'
    }
  }
})
```

Many new Index API methods:

Example "SSR":

```ts
/**
 * Add/remove/change properties of SSR production generated package.json
 *
 * Can be async. Can directly modify the "pkgJson" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSRPackageJson: (
  pkgJson: { [index in string]: any },
  api: IndexAPI
) =>
  | void
  | { [index in string]: any }
  | Promise<void | { [index in string]: any }>;

/**
 * Requires @quasar/app-vite v3.1+
 *
 * Extend the underlying SSR manifest file generated by Vite,
 * which is used by the server-side renderer to know which files to preload.
 *
 * Can be async. Can directly modify the "ssrManifest" parameter or
 * return a new one that will be merged with the default one.
 *
 * Similar in use to /quasar.config > ssr > extendSSRManifestJson
 */
extendSSRManifestJson?: (
  ssrManifest: QuasarSsrManifest
) => void | QuasarSsrManifest | Promise<void | QuasarSsrManifest>;

/**
 * Extend/configure the Workbox GenerateSW options
 * Specify Workbox options which will be applied on top of
 *  `pwa > extendPWAGenerateSWOptions()`.
 *
 * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSRGenerateSWOptions: (
  config: GenerateSWOptions,
  api: IndexAPI
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;

/**
 * Extend/configure the Workbox InjectManifest options
 * Specify Workbox options which will be applied on top of
 *  `pwa > extendPWAInjectManifestOptions()`.
 *
 * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSRInjectManifestOptions: (
  config: InjectManifestOptions,
  api: IndexAPI
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
```

Example "Electron":

```ts
/**
 * Add/remove/change properties of Electron production generated package.json
 *
 * Can be async. Can directly modify the "pkgJson" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendElectronPackageJson: (
  pkgJson: { [index in string]: any },
  api: IndexAPI
) =>
  | void
  | { [index in string]: any }
  | Promise<void | { [index in string]: any }>;
```

Example "PWA":

```ts
/**
 * Should you need some dynamic changes to the /src-pwa/manifest.json,
 * use this method to do it.
 *
 * Can be async. Can directly modify the "json" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendPWAManifestJson: (
  json: PwaManifestOptions,
  api: IndexAPI
) => void | PwaManifestOptions | Promise<void | PwaManifestOptions>;

/**
 * Extend/configure the Workbox GenerateSW options.
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendPWAGenerateSWOptions: (
  config: GenerateSWOptions,
  api: IndexAPI
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;

/**
 * Extend/configure the Workbox InjectManifest options.
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendPWAInjectManifestOptions: (
  config: InjectManifestOptions,
  api: IndexAPI
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
```

Example "BEX":

```ts
/**
 * Should you need some dynamic changes to the Browser Extension manifest file
 * (/src-bex/manifest.json) then use this method to do it.
 *
 * Can be async. Can directly modify the "json" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendBexManifestJson: (
  json: object,
  api: IndexAPI
) => void | object | Promise<void | object>;
```

Example "SSG (v3.1+)":

```ts
/**
 * Extend the Rolldown config that is used for the SSG renderer
 * (which is your /src-ssg/ssg-renderer file).
 *
 * Can be async. Can directly modify the "rolldownConf" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSGRendererConf: (
  cfg: RolldownOptions,
  api: IndexAPI
) => void | RolldownOptions | Promise<void | RolldownOptions>;

/**
 * Extend the underlying SSR manifest file generated by Vite,
 * which is used by the server-side renderer to know which files to preload.
 *
 * Can be async. Can directly modify the "ssrManifest" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSGManifestJson: (
  ssrManifest: QuasarSsrManifest
) => void | QuasarSsrManifest | Promise<void | QuasarSsrManifest>;

/**
 * Extend/configure the Workbox GenerateSW options
 * Specify Workbox options which will be applied on top of
 *  `pwa > extendPWAGenerateSWOptions()`.
 *
 * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSGGenerateSWOptions: (
  config: GenerateSWOptions,
  api: IndexAPI
) => void | GenerateSWOptions | Promise<void | GenerateSWOptions>;

/**
 * Extend/configure the Workbox InjectManifest options
 * Specify Workbox options which will be applied on top of
 *  `pwa > extendPWAInjectManifestOptions()`.
 *
 * https://developer.chrome.com/docs/workbox/the-ways-of-workbox/
 *
 * Can be async. Can directly modify the "config" parameter or
 * return a new one that will be merged with the default one.
 */
api.extendSSGInjectManifestOptions: (
  config: InjectManifestOptions,
  api: IndexAPI
) => void | InjectManifestOptions | Promise<void | InjectManifestOptions>;
```

A new `api.logger` (available on all four scripts: Index, Install, Uninstall, Prompts) prints in the Quasar CLI's own output style and tags every line with your extension id. See [api.logger](../app-extensions/development-guide/index-api.md#api-logger).

There's also new wrappers that `@quasar/app-vite` now supplies for the Index/Prompts/Install/Uninstall scripts. IDE auto-completion, here we come.

The short form of running CLI commands provided by an App Extension has been removed:

```bash
# works, still good; the way to go!
quasar run <ext-id> <cmd> [...args]

# this will NO LONGER WORK:
quasar <ext-id> <cmd> [...args]
```

And the params for [api.registerCommand()](../app-extensions/common-formulas-and-patterns/provide-cli-commands.md) have changed.

We've also massively upgraded the dev setup for AEs. You might want to do a top to bottom read of the AE docs again, starting with [AE Development Guide](../app-extensions/development-guide/introduction.md) and spawn a new AE project folder to take advantage of all the new goodies. **TS variant included!**

## Bird's eye view on what's new

- ⚡ Blazing Fast Compilation: We've replaced esbuild with Rolldown for /src-* folders and completely redesigned the build architecture. Build steps are now parallelized across all Quasar modes, resulting in significantly faster speeds and a smaller footprint for your production distributables.
- ⚙️ Next-Gen Environment Management: We’ve redesigned env file management from the ground up. You will no longer need to restart the dev server when making changes to these files, and you can now use them directly within your quasar.config file too!
- 🔒 Enhanced Security & Modern Standards: We’ve migrated from `process.env` to the modern `import.meta.env` (aligning with Vite's native model) with full TypeScript support. A new security layer ensures client-side files only use a configurable prefix for env definitions, preventing potential leaks of sensitive data.
- 📦 Smarter Dependency Isolation: We now have a clear separation of dependencies for each Quasar mode. You can install mode-specific packages directly in their respective /src-* folders. For example, the default Electron app will no longer require dependencies to be installed in its dist folder—only what you explicitly install in /src-electron will be included.
- 🌍 Redesigned SSR Architecture: SSR mode now features superior support for custom web servers and proper TypeScript integration. When adding SSR, the CLI will prompt you to spawn a preconfigured /src-ssr folder using Hono, Fastify, Express, or Koa (let us know what other out-of-the-box servers you’d like!). Hybrid SSR + partial CSR now available, too! Also, Quasar CLI now handles the critical CSS paths on the development server-side render too, so no more FOUC for it!
- 📂 New Server Assets Folder for SSR: We've introduced a /src-ssr/server-assets folder alongside helpful utility functions. This makes it incredibly easy to reference assets (like HTTPS certificates) across dev and production runtimes, eliminating the strict need for an Apache/Nginx wrapper. We've also made the serverless support a breeze.
- 🚀 New Quasar Mode: [SSG (Static Site Generator)](developing-ssg/introduction.md) with optional PWA takeover and optional partial CSR.
- 🖥️ Revamped Electron Mode: We've added lots of new features to make desktop development smoother. Similar to SSR, we've introduced a /src-electron/electron-assets folder. Referencing files from here (or from the /public folder) is now much easier via new utility methods available in both /src-electron and /src.
- 🛣️ Vue Router: First-class support for the [Filename-Based Routing](page-routing-with-vue-router.md#filename-based-routing).
- 🚀 Smarter reloads (when absolutely needed): You'll notice the DX on dev has improved significantly, with even smarter heuristics when changing the quasar.config file or the dotenv files.
- 🛠️ Modernized Core: The codebase has been updated to take full advantage of Node.js v22+ features, alongside countless other small but significant improvements across all Quasar modes to boost your productivity. The CLI uses significantly less dependencies.

## Start the upgrade

> [!TIP]
> If you are unsure that you won't skip by mistake any of the recommended changes, you can scaffold a new project folder with the @quasar/app-vite v3 at any time and then easily start porting your app from there.
>   
>
> ```bash
> pnpm create quasar@latest
> ```

### PNPM related

If you're using PNPM v11, edit your `/pnpm-workspace.yaml` file. No longer needing the shamefullyHoist config.

Example "/pnpm-workspace.yaml":

```yaml
# https://pnpm.io/settings

allowBuilds:
  '@parcel/watcher': true
  core-js: true
  electron-winstaller: true
  esbuild: true
  lightningcss: true
  rolldown: true
  unrs-resolver: true
```

Also, create a `pnpm-workspace.yaml` file inside `/src-<bex|pwa|electron|ssr>` with this content:

Example "/src-<bex|pwa|electron|ssr>/pnpm-workspace.yaml":

```bash
# This file exists to force pnpm install deps here, regardless of upper workspaces
# https://pnpm.io/settings
```

### /package.json

Edit your `/package.json` on the `@quasar/app-vite` entry:

Example "/package.json":

```json
"devDependencies": {
-   "@quasar/app-vite": "^2.0.0",
+   "@quasar/app-vite": "^3.0.0"
}
```

Make sure you also have Vue Router v5+ too, which is now the minimum version required!

Example "/package.json":

```json
"dependencies": {
  "vue-router": "^5.0.6"
}
```

### Global search and replace

Do a global search for `#q-app/wrappers` and replace with `#q-app`.

In an effort to better align with the Vue ecosystem, Quasar CLI now injects only one alias: `@/`. So please do a global search and replace in your code on your `import` statements like below. Alternatively, you can inject the old aliases yourself (take a look below the table to find out how).

| Alias | Status | Description |
| --- | --- | --- |
| `@/` | **New!** | Points to `/src` and replaces the old `src` alias. |
| `app/` | Removed | Replace import to `@/../` |
| `src/` | Removed | Replace import to `@/` |
| `components/` | Removed | Replace import to `@/components/` |
| `layouts/` | Removed | Replace import to `@/layouts/` |
| `pages/` | Removed | Replace import to `@/pages/` |
| `assets/` | Removed | Replace import to `@/assets/`. Replace `~assets/...` in your .vue files in `<template>` section to `~@/assets/...` too! |
| `boot/` | Removed | Replace code using it by `@/boot/` |
| `stores/` | Removed | Replace code using it by `@/stores/` |

> [!TIP]
> **Alternative to alias changes**
>
> Should you want, you can inject the old aliases yourself and avoid the necessary changes above:
>   
>
> Example "/quasar.config file":
>
> ```js
> import { defineConfig } from '#q-app'
>
> export default defineConfig(ctx => ({
>   build: {
>     alias: {
>       src: ctx.appPaths.srcDir,
>       app: ctx.appPaths.appDir,
>       components: ctx.appPaths.resolve.src('components'),
>       layouts: ctx.appPaths.resolve.src('layouts'),
>       pages: ctx.appPaths.resolve.src('pages'),
>       assets: ctx.appPaths.resolve.src('assets'),
>       boot: ctx.appPaths.resolve.src('boot'),
>       stores: ctx.appPaths.resolve.src('stores')
>     }
>   }
> }))
> ```

Do a global search for `process.env` and replace with `import.meta.env`. For the Quasar supplied constants, you will need to prefix them with `QUASAR_` too. Here's a list:

Example "process.env -> import.meta.env":

```js
// new boolean ones!
import.meta.env.QUASAR_SPA_MODE
import.meta.env.QUASAR_PWA_MODE
import.meta.env.QUASAR_SSR_MODE
import.meta.env.QUASAR_SSG_MODE
import.meta.env.QUASAR_ELECTRON_MODE
import.meta.env.QUASAR_BEX_MODE
import.meta.env.QUASAR_CAPACITOR_MODE
import.meta.env.QUASAR_CORDOVA_MODE

- process.env.DEV
- process.env.PROD
import.meta.env.QUASAR_DEV
import.meta.env.QUASAR_PROD

// notice the DEBUGGING -> DEBUG change!
- process.env.DEBUGGING
import.meta.env.QUASAR_DEBUG

- process.env.MODE
- process.env.TARGET
import.meta.env.QUASAR_MODE
import.meta.env.QUASAR_TARGET

- process.env.CLIENT
- process.env.SERVER
import.meta.env.QUASAR_CLIENT
import.meta.env.QUASAR_SERVER

- process.env.SERVICE_WORKER_FILE
- process.env.PWA_FALLBACK_HTML
- process.env.PWA_SERVICE_WORKER_REGEX
import.meta.env.QUASAR_SERVICE_WORKER_FILE
import.meta.env.QUASAR_PWA_FALLBACK_HTML
import.meta.env.QUASAR_PWA_SERVICE_WORKER_REGEX

- process.env.QUASAR_ELECTRON_PRELOAD_FOLDER
- process.env.APP_URL
import.meta.env.QUASAR_ELECTRON_PRELOAD_FOLDER
import.meta.env.QUASAR_APP_URL

- // removed; use ".cjs" instead:
- process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION
```

For the `/index.html` file, instead of relying on the previous "process.env.X", you can now use:

```html
<!-- old way, REPLACE! -->
- <%= process.env.MY_ENV_VAR_OR_DEFINE %>

<!-- new way: -->
+ <%= importMetaEnv.MY_ENV_VAR_OR_DEFINE %>
<!-- or shorthand: -->
+ %MY_ENV_VAR_OR_DEFINE%

<% if (importMetaEnv.MY_ENV_VAR_OR_DEFINE) { %>Wow!<% } %>
```

### Quasar mode package.json

Edit your /package.json file to remove Quasar mode specific dependencies and move them over to new `/src-<mode>/package.json` (create them!):

Example "/package.json to new /src-<mode>/package.json (BEX)":

```js
+ // create /src-bex/package.json:
{
  "name": "quasar-bex-app",
  "version": "1.0.0",
  "description": "Quasar BEX Folder",
  "private": true,
  "type": "module",
  "devDependencies": {
    "@types/chrome": "^0.2.5" // for TS only
  }
}
```

Example "/package.json to new /src-<mode>/package.json (PWA)":

```js
- // remove from /package.json
{
  "dependencies": {
    "register-service-worker": "^1.7.2"
  },
  "devDependencies": {
    "workbox-build": "^7.0.0",
    "workbox-cacheable-response": "^7.0.0",
    "workbox-core": "^7.0.0",
    "workbox-expiration": "^7.0.0",
    "workbox-precaching": "^7.0.0",
    "workbox-routing": "^7.0.0",
    "workbox-strategies": "^7.0.0"
  }
}

+ // create /src-pwa/package.json:
{
  "name": "quasar-pwa-app",
  "version": "1.0.0",
  "description": "Quasar PWA Folder",
  "private": true,
  "type": "module",
  "dependencies": {
    "register-service-worker": "^1.7.2"
  },
  "devDependencies": {
    "workbox-build": "^7.0.0",
    "workbox-cacheable-response": "^7.0.0",
    "workbox-core": "^7.0.0",
    "workbox-expiration": "^7.0.0",
    "workbox-precaching": "^7.0.0",
    "workbox-routing": "^7.0.0",
    "workbox-strategies": "^7.0.0"
  }
}
```

Example "/package.json to new /src-<mode>/package.json (Electron)":

```js
- // remove from /package.json
{
  "devDependencies": {
    "electron": "^42.0.0"
  }
}

+ // create /src-electron/package.json:
{
  "name": "quasar-electron-app",
  "version": "1.0.0",
  "description": "Quasar Electron Folder",
  "private": true,
  "type": "module",
  "devDependencies": {
    "electron": "^42.0.0"
  }
}
```

Example "/package.json to new /src-<mode>/package.json (SSR)":

```js
+ // create /src-ssr/package.json:
{
  "name": "quasar-ssr-app-express",
  "version": "1.0.0",
  "description": "Quasar SSR server folder",
  "private": true,
  "type": "module",
  "dependencies": {
    "express": "^5.0.0",
    "compression": "^1.8.1",
    "helmet": "^8.1.0"
  },
  "devDependencies": {
    "@types/compression": "^1.8.1", // for TS only
    "@types/express": "^5.0.6" // for TS only
  }
}
```

### Notable /quasar.config file changes

Edit your `/quasar.config` file. These are just the important changes that you need to be aware of:

Example "/quasar.config file":

```js
build: {
-   rawDefine: {},
  define: {}, // values need to be JSON.stringify()

-   env: {},
  defineEnv: {}, // or long form "define" with 'import.meta.env.' prefix in key

  // change to "true" if needed; defaults to "false" now!
  // check your deps too!
  vueOptionsAPI,

-   // removed; deferring to Vite's default
-   polyfillModulePreload,

  // new! Vue Router v5+ filename-based routing
  filenameBasedRouting: boolean | VueRouterVitePluginOptions,

  // NOT async, but it can now also return a new config
  // that will be merged with the default one
  extendTsConfig: (tsConfig: TSConfig) => void | TSConfig,

-   // removed; add your preferred analyzer yourself;
-   // example available below
-   analyze,
},

sourceFiles: {
  // defaults to: 'src-pwa/register-sw' now!
  // change file name or set to your current one:
  pwaRegisterServiceWorker: 'src-pwa/register-service-worker',

  // defaults to 'src-pwa/custom-sw' now!
  // change file name or set to your current one:
  pwaServiceWorker: 'src-pwa/custom-service-worker',
},

cordova: {
  // no longer available; only modern build system
-   noIosLegacyBuildFlag: true,
},

ssr: {
-   extendPackageJson (pkgJson) {},
  // can now be async and optionally return object to be merged with default one
  extendSSRPackageJson (pkgJson) {},

  // @quasar/app-vite v3.1+
  clientSideRenderingRoutes: [],
  // @quasar/app-vite v3.1+
  noPreloadTagRoutes: [],
  // @quasar/app-vite v3.1+
  extendSSRManifestJson (ssrManifestJson) {},

-   extendSSRWebserverConf (esbuildConf) {},
  // can now be async and optionally return object to be merged with default one
  extendSSRWebserverConf (rolldownConf) {},

-   pwaExtendGenerateSWOptions (conf) {},
-   pwaExtendInjectManifestOptions (conf) {},
  // can now be async and optionally return object to be merged with default one
  extendSSRGenerateSWOptions (conf) {},
  // can now be async and optionally return object to be merged with default one
  extendSSRInjectManifestOptions (conf) {},
},

pwa: {
  // new! NOT async, but it can now also return a new config
  // that will be merged with the default one
  extendPWASwTsConfig: (tsConfig: TSConfig) => void | TSConfig,

-   extendManifestJson (json) {},
  // can now be async and optionally return object to be merged with default one
  extendPWAManifestJson (json) {},

-   injectPwaMetaTags: boolean,
  injectPWAMetaTags: boolean,

-   extendGenerateSWOptions (conf) {},
-   extendInjectManifestOptions (conf) {},
  // can now be async and optionally return object to be merged with default one
  extendPWAGenerateSWOptions (conf) {},
  // can now be async and optionally return object to be merged with default one
  extendPWAInjectManifestOptions (conf) {},

-   extendPWACustomSWConf (esbuildConf) {},
  // can now be async and optionally return object to be merged with default one
  extendPWACustomSWConf (rolldownConf) {},
},

electron: {
-   extendPackageJson (pkgJson) {},
  // can now be async and optionally return object to be merged with default one
  extendElectronPackageJson (pkgJson) {},

-   extendElectronMainConf (esbuildConf) {},
-   extendElectronPreloadConf (esbuildConf) {},
  // can now be async and optionally return object to be merged with default one
  extendElectronMainConf (rolldownConf) {},
  // can now be async and optionally return object to be merged with default one
  extendElectronPreloadConf (rolldownConf) {},
},

bex: {
-   extendBexScriptsConf (esbuildConf) {},
  // can now be async and optionally return object to be merged with default one
  extendBexScriptsConf (rolldownConf) {},
},

// new Quasar Mode (SSG) in @quasar/app-vite v3.1+!
ssg: {
  // check the "Configuring SSG" docs page
},
```

Since `build.analyze` has been removed, here is how to manually do it now:

Example "Alternatives for build.analyze (rollup-plugin-visualizer)":

```js
// pnpm/yarn/npm/bun add -D rollup-plugin-visualizer
// ...and yes, rollup-* as rollup plugins are compatible with Rolldown

import { defineConfig } from '#q-app'

export default defineConfig(ctx => {
  return {
    build: {
      vitePlugins: [
        ctx.prod
          ? [
              "rollup-plugin-visualizer",
              {
                open: true,
                filename: ctx.appPaths.resolve.cache("stats.html")
              },
              { client: true }
            ]
          : null
      ]
    }
  }
})
```

Example "Alternatives for build.analyze (vite-bundle-analyzer)":

```js
// pnpm/yarn/npm/bun add -D vite-bundle-analyzer

import { defineConfig } from '#q-app'
import { analyzer } from 'vite-bundle-analyzer'

export default defineConfig(ctx => {
  return {
    build: {
      vitePlugins: [
        ctx.prod
          ? [
              analyzer,
              {
                openAnalyzer: true,
                analyzerMode: "static",
                fileName: ctx.appPaths.resolve.cache("stats.html")
              },
              { client: true }
            ]
          : null
      ]
    }
  }
})
```

The `ctx` object now includes a logger that prints in the Quasar CLI's own output style. See [Logging via ctx](quasar-config-file.md#logging-via-ctx).

You might also want to change the `ssr` prop value fed to the i18n plugin, especially if you plan on using SSG mode too:

Example "/quasar.config file":

```js
// @quasar/app-vite v3.1+
build: {
  vitePlugins: [
    [
      '@intlify/unplugin-vue-i18n/vite',
      {
        // ...
        ssr: ctx.mode.ssr || ctx.mode.ssg
      }
    ]
  ]
}
```

### TypeScript changes

The only `.d.ts` file that you need will be in the root of your project folder:

Example "/env.d.ts":

```ts
/**
 * Add types (that are not auto-magically added by Quasar CLI already)
 * for your custom variables to avoid TypeScript errors, like dynamic
 * process.env variables or definitions in dotenv files configured ONLY
 * for the /quasar.config file itself.
 *
 * @example
 * interface ImportMetaEnv {
 *   readonly MY_VAR: string;
 *   readonly MY_OTHER_VAR: string;
 * }
 */
interface ImportMetaEnv {}
```

You were previously using the following, which needs to be removed from all your `.d.ts` files. It would be a good idea to just have the `/env.d.ts` file defined above.

Example "*env.d.ts":

```ts
/**
 * REMOVE this! No longer needed.
 * Delete the entire block:
 */
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: string
    VUE_ROUTER_MODE: 'hash' | 'history' | 'abstract' | undefined
    VUE_ROUTER_BASE: string | undefined
    // ...along with any other previously Quasar needed defines
  }
}
```

You can read about [Handling import.meta.env](handling-import-meta-env.md). Highly recommended as it will show you all the new goodies.

### Install new deps

Then pnpm/yarn/npm/bun install in the root folder and run `quasar prepare`. Restart your IDE to make sure the new dependencies have been correctly picked up.

Make sure to update your `/quasar.config` file with the newest specs in order to satisfy the types. Check all following sections.

## Notable breaking changes

- All imports from `#q-app/wrappers` need to be replaced with `#q-app`. Do a global search and replace.
- Switched from `process.env` to the modern `import.meta.env`. [Link](handling-import-meta-env.md)
- /quasar.config > build > vueOptionsAPI is now `false` by default. Check other Vue 3 packages and components. If they use the `Options` API, you will need to set this to true.
- /quasar.config > build > polyfillModulePreload (removed and now defaulting to Vite's own config for this)
- /index.html -> new [HTML Constant Replacement](handling-import-meta-env.md#the-index-html-file). `<% if (process.env.X) %>` will no longer work.
- New [dotenv files support](handling-import-meta-env.md#more-on-dotenv-files), including for the `/quasar.config` file itself. By default, Quasar CLI will only look for `.env` and `.env.local`, but you can add other files as well to support the ones with prod/dev/mode suffixes.
- /quasar.config > `env` is now used by the new dotenv support. Dropped `build > rawDefine` as well. Use the new build > `define` & `defineEnv` instead. [Link](handling-import-meta-env.md#adding-to-import-meta-env)
- Boot files & preFetch > redirect() usage has changed. Need to return immediately after calling it. No longer supporting throwing an error (or returning a Promise) with the `{ url }` syntax. Directly use `redirect()` instead.
- The /quasar.config file can come with `.js` or `.ts` extensions only. Dropped support for `.cjs`, `.mjs`, `.cts` and `.mts`.
- All Quasar Modes now need to install their specific dependencies directly under their `/src-<mode>` folder.
- Replaced `esbuild` tool with `Rolldown` for all the specific Quasar mode files (under their `/src-<mode>` folders). This also has impact over all the /quasar.config `extendX()` methods, as they will now receive a Rolldown config object.
- All /quasar.config `extendX()` methods can now be async and optionally return a (Rolldown/etc) config that will be merged with the default one.
- Dropped support for Capacitor v4 and below
- Dropped support for `@electron/packager` v18 and below
- Cordova with iOS now uses the modern build system. The `noIosLegacyBuildFlag` has been removed.
- The "quasar dev -m cordova" command now opens up the corresponding IDE instead (just like Capacitor mode)
- The "quasar dev/build -m bex" command now defaults to "chrome" target, so the `-t|--target` option can be ommitted.

## Electron mode changes

We are introducing `quasarRuntime`. [More info](developing-electron-apps/electron-accessing-files.md).

Move your `/src-electron/icons` to `/src-electron/electron-assets/icons` (create the new `electron-assets` folder).

### Preload script

Example "/src-electron/electron-preload (Optional!)":

```js
/**
 * Only one preload script should contain this
 */

import { contextBridge } from 'electron'
import { quasarRuntime } from '#q-app/electron/preload'

/**
 * Can be used in the renderer process through `window.quasarRuntime`
 */
contextBridge.exposeInMainWorld('quasarRuntime', quasarRuntime)
```

### Main script

And you might also want to update your `/src-electron/electron-main` script:

Example "/src-electron/electron-main (Diff)":

```js
- import { fileURLToPath } from 'url'
- const currentDir = fileURLToPath(new URL('.', import.meta.url))

import {
  registerQuasarRuntime,
  resolveElectronAssetsPath
} from "#q-app/electron/main"

- let mainWindow: BrowserWindow | undefined;
- async function createWindow() {
-   mainWindow = new BrowserWindow({
-     icon: path.resolve(currentDir, 'icons/icon.png'), // tray icon
-     webPreferences: {
-       preload: path.resolve(
-         currentDir,
-         path.join(process.env.QUASAR_ELECTRON_PRELOAD_FOLDER, 'electron-preload' + process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION)
-       ),
-     },
async function createWindow() {
  const mainWindow = new BrowserWindow({
    icon: resolveElectronAssetsPath("icons/icon.png"), // Windows and Linux
    webPreferences: {
      preload: path.join(import.meta.dirname, "electron-preload.cjs")
    },

-    if (process.env.DEV) {
-      await mainWindow.loadURL(process.env.APP_URL);
-    }
   if (import.meta.env.QUASAR_DEV) {
     await mainWindow.loadURL(import.meta.env.QUASAR_APP_URL);
   }

-    mainWindow.on('closed', () => {
-      mainWindow = undefined;
-    });
}

- void app.whenReady().then(createWindow);
- app.on('window-all-closed', () => {
-   if (platform !== 'darwin') {
-     app.quit();
-   }
- });
- app.on('activate', () => {
-   if (mainWindow === undefined) {
-     void createWindow();
-   }
- });
void app.whenReady().then(() => {
  registerQuasarRuntime();
  void createWindow();
  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      void createWindow();
    }
  });
});
app.on("window-all-closed", () => {
  if (platform !== "darwin") {
    app.quit();
  }
});
```

Example "/src-electron/electron-main (Full (TypeScript))":

```ts
import { BrowserWindow, app } from "electron";
import path from "node:path";
import os from "node:os";
import {
  registerQuasarRuntime,
  resolveElectronAssetsPath
} from "#q-app/electron/main";

// needed in case process is undefined under Linux
const platform = process.platform || os.platform();

async function createWindow() {
  /**
   * Initial window options
   */
  const mainWindow = new BrowserWindow({
    icon: resolveElectronAssetsPath("icons/icon.png"), // Windows and Linux
    width: 1000,
    height: 600,
    useContentSize: true,
    webPreferences: {
      contextIsolation: true,
      // https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/electron-preload-script
      preload: path.join(import.meta.dirname, "electron-preload.cjs")
    }
  });

  if (import.meta.env.QUASAR_DEV) {
    await mainWindow.loadURL(import.meta.env.QUASAR_APP_URL);
  } else {
    await mainWindow.loadFile("index.html");
  }

  if (import.meta.env.QUASAR_DEBUG) {
    // if on DEV or Production with debug enabled
    mainWindow.webContents.openDevTools();
  } else {
    // we're on production; no access to devtools pls
    mainWindow.webContents.on("devtools-opened", () => {
      mainWindow?.webContents.closeDevTools();
    });
  }
}

void app.whenReady().then(() => {
  registerQuasarRuntime();
  void createWindow();

  app.on("activate", () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      void createWindow();
    }
  });
});

app.on("window-all-closed", () => {
  if (platform !== "darwin") {
    app.quit();
  }
});
```

### Other Electron Mentions

- Don't forget to look over the [Installing Electron Dependencies](developing-electron-apps/installing-electron-dependencies.md) page.
- You might want to check out the updated [Frameless Electron Window](developing-electron-apps/frameless-electron-window.md) page.

## SSR mode changes

We've added way better support for non-Express.js webservers and highly improved typings. When the SSR mode is added to a project, the Quasar CLI will ask what webserver you would like to use. You can pick from Hono/Express.js/Fastify/Koa.

Quasar CLI now also handles the critical CSS paths on the development server-side render too! No more FOUC for it. This feature was added in `@quasar/app-vite` v3.2.

Instead of diffing here, you might want to check the next pages (even if you still want to stay with Express.js):

- [Installing SSR Dependencies](developing-ssr/installing-ssr-dependencies.md)
- New ability to run as [Hybrid SSR + partial CSR](developing-ssr/hybrid-ssr-with-partial-csr.md)
- Webserver: check out examples with Hono/Express/Fastify/Koa: [SSR Webserver](developing-ssr/ssr-webserver.md); or remove and add SSR mode again.
- Middlewares: check out examples with Hono/Express/Fastify/Koa: [SSR Middleware](developing-ssr/ssr-middleware.md); or remove and add SSR mode again.
- Check out the [SSR Handling of 404 and 500 Errors](developing-ssr/handling-404-and-500-errors.md) page.
- If using a serverless architecture, then check out the new [Serverless](developing-ssr/ssr-webserver.md#serverless) section in SSR Webserver page.
- You might also want to use thew new `/src-ssr/server-assets` folder (create it). This is copied as-is to dist and can be used in dev too, through `resolve.serverAssets()` or `folders.serverAssets`.
- One more thing to note, for SSR middlewares: `serve.error()` has been changed to `serve.devError()` (with new params).

## PWA mode changes

### `/src-pwa/sw/` subfolder for the service worker

The service worker and the PWA-specific `tsconfig.json` (for TypeScript projects) now lives inside `/src-pwa/sw/`. The main-thread file `register-sw.{js,ts}` stays at the `/src-pwa/` root.

Background: TypeScript does NOT pick up nested `tsconfig.json` files when running `tsc`/`vue-tsc` from the project root, so the previous flat layout (`src-pwa/custom-sw.ts` + sibling `tsconfig.json`) produced false errors like `Property 'skipWaiting' does not exist on type 'ServiceWorkerGlobalScope'`. Also, you could not use DOM types (e.g., `location.reload()`) inside the register service worker script when it works perfectly in runtime. Quasar's generated `.quasar/tsconfig.json` now auto-excludes `/src-pwa/sw/` so root tsc/vue-tsc skip it, it is checked separately, and the nested tsconfig handles SW typing in the IDE as before.

Migration steps:

1. Create `/src-pwa/sw/`.
2. Move `/src-pwa/custom-sw.{js,ts}` -> `/src-pwa/sw/custom-sw.{js,ts}`.
3. TS only: move `/src-pwa/tsconfig.json` -> `/src-pwa/sw/tsconfig.json` then replace the contents with a thin pointer to the Quasar-generated SW config:
Example "/src-pwa/sw/tsconfig.json":

```json
{
  "extends": "../../.quasar/tsconfig.pwa-sw.json"
}
```

 4. Update your ESLint config glob:
```js
{
-   files: ['src-pwa/custom-service-worker.ts'],
+   files: ['src-pwa/sw/**/*.ts'],
  languageOptions: {
    globals: {
      ...globals.serviceworker
    }
  }
}
```

 5. If you set `sourceFiles.pwaServiceWorker` explicitly in `quasar.config`, update it:
```js
sourceFiles: {
-   pwaServiceWorker: 'src-pwa/custom-service-worker',
+   pwaServiceWorker: 'src-pwa/sw/custom-sw',
}
```

 6. (Optional) TypeScript + ESLint only: to type-check the SW during dev/build, add a `typescript` entry to your `vite-plugin-checker` options (alongside `vueTsc: true`):

Example "/quasar.config.ts":

```js
vitePlugins: [
  [
    'vite-plugin-checker',
    {
      vueTsc: true,
+       typescript: {
+         tsconfigPath: './src-pwa/sw/tsconfig.json'
+       }
      // ...
    },
    { server: false }
  ]
]
```

7. (Optional) TypeScript: add a `package.json` script to check both root and SW types:
Example "/package.json":

```json
"scripts": {
+  "typecheck": "vue-tsc --noEmit && tsc --project src-pwa/sw/tsconfig.json --noEmit",
  // ...
}
```

## Capacitor mode changes

### capacitor.config in js/ts form

The new @quasar/app-vite adds support for `capacitor.config.js` and `capacitor.config.ts` files, and drops support for `capacitor.config.json`. The .js and .ts variants are much more flexible and do not have the git noise of the .json one, which was being rewritten on every "quasar dev" / "quasar build" with relevant fields. You must migrate to `capacitor.config.ts` (for TypeScript projects) or `capacitor.config.js` (for JS projects) before upgrading, more details below.

The "quasar mode add capacitor" command now scaffolds `capacitor.config.js` for JS projects, or a `capacitor.config.ts` for TypeScript projects. See [Configuring Capacitor](developing-capacitor-apps/configuring-capacitor.md) for more information. Config files use the new `defineCapacitorConfig` helper from "@quasar/app-vite/capacitor":

```ts
import { defineCapacitorConfig } from '@quasar/app-vite/capacitor';

export default defineCapacitorConfig({
  appId: 'org.example.app',
  appName: 'My App'
});
```

The helper defaults `webDir` to `'www'`, injects `server.url` (and `server.cleartext: true` on Android) in dev mode, and types your input against CapacitorConfig from "@capacitor/cli". Your own values always win, and the source file isn't mutated. Inside the config, `import.meta.env.QUASAR_DEV`, `QUASAR_TARGET`, `QUASAR_APP_URL`, and your own `.env` / `build.env` values are available. Read [Configuring Capacitor](developing-capacitor-apps/configuring-capacitor.md#reading-env-values) for more information.

To migrate from ".json", replace the file with a `defineCapacitorConfig({...})` call carrying the same fields. `webDir` can be dropped:

Example "Migrating from capacitor.config.json":

```ts
- {
-   "appId": "org.example.app",
-   "appName": "My App",
-   "webDir": "www"
- }
import { defineCapacitorConfig } from '@quasar/app-vite/capacitor';

export default defineCapacitorConfig({
  appId: 'org.example.app',
  appName: 'My App'
});
```

### Removed: quasar.config > capacitor.{appName, version, description}

Three fields under quasar.config > capacitor are gone. None of them did what they appeared to.

The `version` and `description` were never read by the Capacitor CLI, neither from capacitor.config.* nor from src-capacitor/package.json. iOS and Android take their versions from android/app/build.gradle (`versionName` / `versionCode`) and ios/App/App/Info.plist (CFBundleShortVersionString / CFBundleVersion). Edit those directly when bumping for a store release. See [Publishing to Store](developing-capacitor-apps/publishing-to-store.md).

The `appName` had some effect, but it was limited. Capacitor writes it into Info.plist's "CFBundleDisplayName" (iOS) and "strings.xml" > app_name (Android), but only at "cap add" time. The "cap sync" and "cap copy" commands don't re-run that step, so a quasar.config file field suggested a live setting it wasn't. It's now captured via a prompt during "quasar mode add capacitor", written into the scaffolded capacitor.config.*, and applied to the native projects when you add the platform. Later renames happen by editing Info.plist and strings.xml directly, or by removing and re-adding the platform.

If you were setting any of these, remove them:

Example "/quasar.config file":

```js
capacitor: {
-   appName: 'My App',
-   version: '1.2.0',
-   description: 'My great app'
  // hideSplashscreen, capacitorCliPreparationParams remain
}
```

### src-capacitor/package.json no longer rewritten

Quasar used to overwrite "name", "version", "description", and "author" in src-capacitor/package.json on every "quasar dev/build" command. Capacitor's CLI doesn't read most of that, so the rewrites were churn for no benefit (and noise in git). New projects scaffold a static `quasar-capacitor-app` / `1.0.0` template. Existing projects can update theirs to match, or leave it alone. Quasar won't touch it either way.

## New Quasar Mode: SSG *(@quasar/app-vite v3.1+)*

Read more about the new [SSG (Static Site Generator)](developing-ssg/introduction.md) Quasar Mode.

## Other considerations

### Switching to Oxlint and Oxfmt

You may also want to switch your linting and formatting to `oxlint` and `oxfmt`. In our opinion, this is the future anyway. At some point in the near future, Quasar's project scaffolding package will only offer this for linting.

As of writing these lines, the support for `.vue` files is not yet fully ready, but you will still be able to enjoy it a lot.

[More info](lint-and-format-code.md#oxlint-oxfmt)

### Filename-based routing with Vue Router v5+

We now have first-class support for Vue Router's filename-based routing. You might want to [give it a try](page-routing-with-vue-router.md#filename-based-routing).

### Upgrade to @quasar/extras v2

Optionally (but highly recommended) also upgrade to the new `@quasar/extras` v2: [Release notes](https://github.com/quasarframework/quasar/releases/tag/%40quasar%2Fextras-v2.0.0).

### New CLI command options

#### For all commands: --no-color

By default, all CLI commands output colored text in the terminal (when not running in a CI environment). Should you wish to avoid this, use the `--no-color` when you run any of the CLI commands.

#### For build command: --no-summary

Should you want your build to skip printing the build summary (and thus being slightly faster) after building your app:

```bash
quasar build --no-summary
```

### Running AE commands

The short form of running CLI commands provided by an App Extension has been removed:

```bash
# works, still good; the way to go!
quasar run <ext-id> <cmd> [...args]

# this will NO LONGER WORK:
quasar <ext-id> <cmd> [...args]
```

### CSP (Content Security Policy)

You may want to add a CSP meta tag in your `/index.html`. This is especially useful for Electron mode where a warning about the lack of one is displayed, but it's a good security measure for all Quasar Modes too:

```html
<!doctype html>
<html>
  <head>
    <!-- add to the head -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';<% if (ctx.dev) { %> connect-src 'self' ws://localhost:*; worker-src 'self' blob:;<% } %>"
    />
  </head>
</html>
```

> [!NOTE]
> This works great with Oxlint and Oxfmt. However, the above might need a bit of tweaking when using ESLint and vite-plugin-checker.
