Migrating to Root.js v2
Root.js v2 updates third-party dependencies, including Vite, Express, Sass and esbuild. Keep the following in mind when you update a project.
Node.js updates
Applies to v2.0.0 and above
Support for non-LTS versions of Node.js has been dropped. Root.js v2 requires Node.js 20 or later. (Root.js v3 requires Node.js 24, see the v3 migration guide.)
Vite config updates
Applies to v2.2.0 and above
Vite has been updated to v7. See Vite's migration guide for details.
Vite no longer supports the legacy Sass API. Root.js converts old settings for you, but you should rename includePaths to loadPaths in root.config.ts:
⏪ Before:
// @/root.config.ts
import {defineConfig} from '@blinkk/root';
export default defineConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
includePaths: [/* scss paths */],
},
},
},
},
});
⏩ After:
// @/root.config.ts
import {defineConfig} from '@blinkk/root';
export default defineConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
loadPaths: [/* scss paths */],
},
},
},
},
});
Sass updates
Applies to v2.2.0 and above
Sass has been updated to the latest sass-embedded (v1.92.0 at the time of writing), which has several breaking changes.
In particular, the way Sass handles mixed declarations has changed, which can change the order of your CSS output:
⏪ Before:
/* SCSS input */
.example {
color: red;
&--serious {
font-weight: bold;
}
font-weight: normal;
}
/* CSS output */
.example {
color: red;
font-weight: normal;
}
.example--serious {
font-weight: bold;
}
⏩ After:
/* SCSS input */
.example {
color: red;
&--serious {
font-weight: bold;
}
font-weight: normal;
}
/* CSS output */
.example {
color: red;
}
.example--serious {
font-weight: bold;
}
.example {
font-weight: normal;
}
CMS updates
Applies to v2.2.0 and above
Much of the CMS UI was rewritten to perform better on large projects, which required a breaking change: every schema.define() name must now be unique across the project. This reduces the size of the schemas sent to the CMS and of root-cms.d.ts.
❌ Bad:
/* @/components/A.schema.ts */
const Image = schema.define({
name: 'Image',
fields: [...],
});
export default schema.define(...);
/* @/components/B.schema.ts */
const Image = schema.define({
name: 'Image',
fields: [...],
});
export default schema.define(...);
✅ Good:
/* @/components/Image.schema.ts */
export default schema.define({
name: 'Image',
fields: [...],
});
/* @/components/A.schema.ts */
import Image from './Image.schema.ts';
export default schema.define(...);
/* @/components/B.schema.ts */
import Image from './Image.schema.ts';
export default schema.define(...);
Other issues
Found an issue that isn't covered here? File an issue on GitHub.