Configuration
6. When do you export a function from vite.config instead of an object?
When the config depends on the command, the mode or the SSR flag. defineConfig accepts a function that receives command ('serve' or 'build'), mode, isSsrBuild and isPreview, and it can be async.
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), '')
return {
plugins: [react()],
define: {
__APP_VERSION__: JSON.stringify(env.APP_VERSION),
},
build: {
sourcemap: command === 'build' && mode === 'staging',
},
}
})
A weak answer reads process.env.NODE_ENV at the top of the config to branch on mode. That value is not set from .env files when the config is evaluated, so branches written that way pick the wrong path in CI.
A strong candidate uses the mode argument and loadEnv.
7. How do TypeScript path aliases work in Vite 8?
Vite 8 can read compilerOptions.paths from tsconfig itself. Set resolve.tsconfigPaths: true; the default is false because resolution has a small performance cost.
Vite 8.3 added a top-level tsconfig option that points Vite at a specific TypeScript config file, resolved from the project root, which helps in monorepos where the root tsconfig.json is a solution file with only references.
Before Vite 8 the standard answer was a plugin such as vite-tsconfig-paths or duplicating each alias in resolve.alias. Candidates who still maintain both lists should be asked how they keep them in sync.
The strong answer: pick one source of truth, and remember that tests run through Vitest pick up the same Vite resolution, so the aliases work there too.
8. What does this config produce in the browser bundle?
export default defineConfig({
define: {
__API_URL__: 'https://api.example.com',
__BUILD_TIME__: Date.now(),
'process.env': process.env,
},
})
Answer: three different problems. define replaces identifiers with the value as raw code, and the shared options reference says the value must be a string containing a JSON-serializable value.
__API_URL__ is inserted as https://api.example.com without quotes, which is not a valid expression. Wrap it in JSON.stringify.
__BUILD_TIME__ is a number, which serializes to a valid literal, but it should still go through JSON.stringify for consistency with the documented contract.
'process.env': process.env inlines every environment variable on the build machine, including CI tokens, into any file that reads process.env. That is a secret leak.
A senior candidate also mentions that Vite 8 changed define so object values no longer share a reference: each replaced site gets its own copy, so mutating one does not affect another.
9. How do you control chunking in a Vite 8 build?
Through Rolldown's build.rolldownOptions.output.codeSplitting option.
Vite 8 removed the object form of output.manualChunks and deprecated the function form. codeSplitting takes a list of groups, each with a name, a test pattern and optional priority, minSize, maxSize and minShareCount, documented in the Rolldown reference.
export default defineConfig({
build: {
rolldownOptions: {
output: {
codeSplitting: {
groups: [
{ name: 'react', test: /node_modules[\\/](react|react-dom|scheduler)[\\/]/, priority: 20 },
{ name: 'vendor', test: /node_modules/, priority: 10, minSize: 20000 },
],
},
},
},
},
})
The tradeoff question to follow with: one large vendor chunk caches well across deploys but makes the first visit download code that most routes never use.
A strong candidate measures with a bundle visualizer or Vite Devtools before splitting, and keeps route-level splitting to dynamic import() rather than config.
10. What does the default build target mean, and when do you change it?
Since Vite 7 the default build.target is 'baseline-widely-available': browsers that support features in the Baseline Widely Available set as of a date fixed for each major release.
For Vite 8 that is Chrome 111, Edge 111, Firefox 114 and Safari 16.4, per the build guide. Vite lowers syntax to that target but does not add polyfills.
Change it when analytics show real users on older browsers, typically older iOS Safari on devices that cannot upgrade. The lowest accepted target is es2015, and passing two versions of the same browser now errors.
Candidates who need legacy browsers should mention @vitejs/plugin-legacy and its limit in Vite 8: transforming to ES5 or below is no longer supported.
11. Find the bug: a monorepo dev server that cannot load a shared package.
// apps/web/vite.config.ts
export default defineConfig({
server: {
fs: {
allow: ['src'],
},
},
optimizeDeps: {
include: ['@acme/ui'],
},
})
Bug: setting server.fs.allow turns off Vite's workspace root detection, so the list now permits only src. Files from packages/ui and from the hoisted root node_modules fall outside the allow list, and the dev server refuses to serve them.
The server options reference spells out that setting the option disables the automatic detection.
Fix: allow the workspace root, for example with searchForWorkspaceRoot(process.cwd()) plus any extra folders.
The second issue: a linked workspace package that you edit should usually go in optimizeDeps.exclude, not include, so changes reach the browser without a re-optimize.
A senior candidate also says never to set server.fs.strict: false on a dev server reachable from the network, because the server.fs.deny defaults (.env, .env.*, certificate and key files, .npmrc, .yarnrc.yml and .git) exist for a reason.