Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Vite

Payload has a Vite bundler that you can install and bundle the Admin Panel with. This is an alternative to the Webpack bundler and might give some performance boosts to your development workflow.

To use Vite as your bundler, first you need to install the package:

1
yarn add @payloadcms/bundler-vite

Then you will need to add the bundler to your Payload config:

1
import { buildConfig } from '@payloadcms/config'
2
import { viteBundler } from '@payloadcms/bundler-vite'
3
4
export default buildConfig({
5
collections: [],
6
admin: {
7
bundler: viteBundler(),
8
}
9
})

Vite works fundamentally differently than Webpack. In development mode, it will first pre-bundle any of your dependencies that are CommonJS-only, and then it'll leverage ESM directly in your browser for a better HMR experience.

It then uses Rollup to create production builds of your admin UI. With Vite, you should see a decent performance boost—especially after your first cold start. However, that first cold start might take a few more seconds.

This is because Vite aliases work fundamentally differently than Webpack aliases, and Payload relies on aliasing server-only code out of the Payload config to ensure that the bundled admin JS works within your browser.

Here are the main differences between how Vite aliases work and how Webpack aliases work.

Vite aliases do not work with absolute paths.

In Vite, alias keys must exactly match a import paths. If you have 2 files that import the same server-only module, but have different import paths, you would need to add 2 aliases to support both import paths.

1
// File A
2
import serverOnlyModule from '../server-only-module'
3
4
// File B
5
import serverOnlyModule from '../../server-only-module'
6
7
// payload.config.ts
8
// You would need to add 2 aliases to support both import paths
9
export const buildConfig({
10
collections: [],
11
admin: {
12
bundler: viteBundler(),
13
vite: (incomingViteConfig) => {
14
const existingAliases = incomingViteConfig?.resolve?.alias || {};
15
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
16
17
// Pass the existing Vite aliases
18
if (Array.isArray(existingAliases)) {
19
aliasArray = existingAliases;
20
} else {
21
aliasArray = Object.values(existingAliases);
22
}
23
24
// Add your own aliases using the find and replacement keys
25
aliasArray.push({
26
find: '../server-only-module',
27
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
28
find: '../../server-only-module',
29
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
30
});
31
32
return {
33
...incomingViteConfig,
34
resolve: {
35
...(incomingViteConfig?.resolve || {}),
36
alias: aliasArray,
37
}
38
};
39
},
40
}
41
})

Vite aliases do not get applied to pre-bundled dependencies.

This especially affects plugins, as plugins will be pre-bundled by Vite using esbuild. To get around this and support Vite, plugin authors need to configure an alias to their plugin at the top level, so that the alias will work accordingly.

Here's an example. Say your plugin is called payload-plugin-cool. It's imported as follows:

1
import { myCoolPlugin } from 'payload-plugin-cool'

That plugin should create an alias to support Vite as follows:

1
{
2
// aliases go here
3
find: 'payload-plugin-cool',
4
replacement: path.resolve(__dirname, './my-admin-plugin.js')
5
}

This will effectively alias the entire plugin and work with Vite. If the plugin requires admin-specific code, then the ./my-admin-plugin.js alias target file should reflect any changes necessary to the admin UI that the main server-side plugin performs.

Extending the Vite config

The Payload config supports a new property for plugins to be able to extend the Vite config specifically. That property exists on the main Payload config under admin.vite. You can check out the Vite docs for more information on what you can do with the Vite config.

It's a function that takes a Vite config, and returns an updated Vite config. Here's an example:

1
export const buildConfig({
2
collections: [],
3
admin: {
4
bundler: viteBundler(),
5
vite: (incomingViteConfig) => {
6
const existingAliases = incomingViteConfig?.resolve?.alias || {};
7
let aliasArray: { find: string | RegExp; replacement: string; }[] = [];
8
9
// Pass the existing Vite aliases
10
if (Array.isArray(existingAliases)) {
11
aliasArray = existingAliases;
12
} else {
13
aliasArray = Object.values(existingAliases);
14
}
15
16
// Add your own aliases using the find and replacement keys
17
aliasArray.push({
18
find: '../server-only-module',
19
replacement: path.resolve(__dirname, './path/to/browser-safe-module.js')
20
});
21
22
return {
23
...incomingViteConfig,
24
resolve: {
25
...(incomingViteConfig?.resolve || {}),
26
alias: aliasArray,
27
}
28
};
29
},
30
}
31
})

Learn more about aliasing server-only modules.

Even though there is a new property for Vite configs specifically, we have implemented some "compatibility" between Webpack and Vite out-of-the-box.

If your config specifies Webpack aliases, we attempt to leverage them automatically within the Vite config. They are merged into the Vite alias configuration seamlessly and may work out-of-the-box.

Next

Environment Variables in Admin UI