Skip to content

Upgrading to 3.0

3.0 has one change: the server no longer serves the Studio. The site mounts it, as a router, like anything else in Express.

Before 3.0, @trokky/trokky served the admin UI by dynamically importing @trokky/studio/dist/server/assets.js, a build artifact, behind a type-check suppression. That made the two packages depend on each other, promoted a private path to public API, and failed silently: if the import broke, the server started normally and /studio was a 404 with no explanation. Owning the mount in your server file makes a missing Studio a build error you can see, and lets the server package stop knowing the UI exists.

import { startServer } from '@trokky/trokky/express'
import { studioRouter } from '@trokky/studio/express'
import config from './trokky.config.js'
const server = await startServer(config)
server.app.use('/studio', studioRouter({ apiPath: '/api' }))
// then your own routes, static files and frontend catch-all, as before

Mount it before anything that catches all paths, such as a frontend handler or express.static at the root, or that will swallow /studio. If you use TrokkyExpress.create() and mount() yourself, the same line goes right after mount().

@trokky/studio was already a dependency of every site that showed the Studio, so nothing new to install.

// before
studio: {
enabled: true,
path: '/studio',
structure,
branding: { title: 'My CMS' },
}
// after
structure,
studio: {
branding: { title: 'My CMS' },
}
  • structure moves to the top level. The server reads it to enforce singletons and to refuse to start when structure and schemas disagree; it was never only the Studio’s.
  • studio.enabled and studio.path are gone: whether and where the Studio is served is decided by the app.use() line.
  • studio.apiUrl and studio.requireAuth are gone: neither did anything.
  • studio.branding, studio.fields, studio.settings and studio.session stay. They are served to the UI over the API.

The server refuses to start on the old shape and prints, for each removed key, what to do instead. You cannot upgrade and miss it.

3.1 adds studio.url, the public URL of the mounted Studio (default: the STUDIO_URL environment variable). It replaces two knobs that described the same fact: the device-flow approval page is now <studio.url>/auth/device (with oauth2.verificationUri kept as an override), and system emails link from it. Set it once:

studio: {
url: process.env.STUDIO_URL, // https://cms.example.com/studio
branding: { title: 'My CMS' },
}

3.1 also stops pretending about two keys nothing read: studio.settings and studio.fields are ignored with a boot warning. studio.session now actually reaches the Studio; it did not before.

  • mount() no longer takes studioPath, and getMountedPaths() returns { apiPath } only.
  • getInfo() from startServer no longer reports studioPath or studioEnabled, so a startup banner that printed the Studio URL needs its own constant.
  • The device-flow approval page defaulted to ${issuer}/studio/auth/device and still does; if you mount the Studio at another path, set oauth2.verificationUri.
  • @trokky/studio no longer exports ./dist/server/assets.js. If you imported getStudioHTML or getStudioAsset from there, they are exported from @trokky/studio/express and from the package root.
  • Middleware that skipped /studio by prefix, such as cache headers or redirects, keeps working if you keep the same path. It is your path now, so keep the two in sync.
  • Anything that derived the Studio’s API URL from the request Host header, such as a proxy fix in server.lifecycle.beforeStart, can usually be replaced by passing backendUrl to studioRouter().
  • Cloudflare or CDN rules that exclude /studio from caching are unaffected as long as the mount path stays /studio.

trokky migrate detects the old shape and prints these instructions for your files. trokky create scaffolds the new shape.