Upgrading

Contents

Requirements

Next.XP 6 requires Enonic XP 8.1 or later, Guillotine 9, and Next.XP app 6. Guillotine 8 is required for Next.XP 5 on Enonic XP 8.

Upgrading from v5.x

Compare your project with starter-nextjs while following these steps; the proxy, API routes, and next.config.js are easiest to copy from it.

  1. Dependencies: Update @enonic/nextjs-adapter to ^6.0.0. On the XP side, install Guillotine 9 and Next.XP app 6.

  2. ENONIC_API: Point it to the XP API root instead of the /site endpoint:

    # Before (v5.x)
    ENONIC_API=http://localhost:8080/site
    
    # After (v6.x)
    ENONIC_API=http://localhost:8080/api

    The Guillotine endpoint is ENONIC_API + / + GUILLOTINE_API (optional, default com.enonic.app.guillotine:graphql). Media URLs use ENONIC_MEDIA_CDN (optional, default ENONIC_API). Use localhost rather than 127.0.0.1 locally, so draft media loads with the Content Studio session.

  3. Guillotine 9 query syntax: Pass siteKey, project, and branch to the guillotine() field:

    # Before (v5.x)
    query($path:ID!) {
      guillotine {
        get(key: $path) { ... }
      }
    }
    
    # After (v6.x)
    query {
      guillotine(siteKey: $siteKey, branch: $branch, project: $project) {
        get(key: $path) { ... }
      }
    }

    The adapter supplies $siteKey, $branch, $project, and $path. Query and variables functions now receive them as a GlobalVariables object instead of the content path; a variables function must return them, optionally with custom variables:

    import type {GlobalVariables} from '@enonic/nextjs-adapter';
    
    export function commonVariables(vars: GlobalVariables) {
        return vars;
    }
  4. Media and page URLs: imageUrl, mediaUrl, attachmentUrl, and pageUrl return objects in Guillotine 9. Query them with the adapter’s helpers and render them with the matching functions, replacing getUrl(), getAsset(), and type: absolute:

    // Before (v5.x)
    imageUrl: imageUrl(type: absolute, scale: "width(500)")
    <img src={getUrl(photo.imageUrl, meta)}/>
    
    // After (v6.x)
    ${imageUrlQuery({scale: 'width(500)'})}
    <img src={imageUrl(photo.imageUrl)}/>
    
    ${pageUrlQuery()}
    <Link href={pageUrl(parent.pageUrl, meta)}>Back</Link>

    Reference files in public/ by their plain path, for example /images/logo.svg. Remove any media.defaultBaseUrl configuration in XP.

  5. Proxy: Replace src/proxy.ts with the starter’s version. It validates the xp parameter, enables draft mode through /api/preview, rewrites prefix-less URLs to the internal default-locale route, and redirects /<default locale>/…​ to /…​. URLs without the default-locale prefix were already supported in v5; redirecting explicitly prefixed URLs is new in v6. For example, with en as the default, /about continues to work and /en/about now redirects to /about.

  6. Encrypted API routes: Replace the token check with blob validation in /api/preview and /api/revalidate, and copy src/utils.ts from the starter:

    // Before (v5.x)
    import {validateToken} from '../../../utils';
    
    // After (v6.x)
    import {validateBlob} from '../../../utils';

    validateBlob() returns {ok: true, params} on success and {ok: false, response} otherwise, so routes can read the decrypted xpProject. ENONIC_API_TOKEN is still required; it is now the encryption key. /api/preview enables draft mode, records the build in a __next_build_id cookie, and redirects to the content path without the xp parameter. /api/revalidate revalidates pageUrl({path}, {locale}) for the project’s locale.

  7. /api/mappings: Add the route from the starter. It returns localizeMappings(MAPPINGS, mapping) for the project in the blob.

  8. /api/renderable: Delete the route. It is no longer called.

  9. next.config.js: Add env.BUILD_ID and the CORS headers from the starter. The allowed origin is derived from the optional ENONIC_ADMIN variable, falling back to the origin of ENONIC_API:

    const config = {
        env: {
            BUILD_ID: Date.now().toString(36),
        },
        // ...
    };
    
    // response headers for '/:path*'
    {key: 'Access-Control-Allow-Origin', value: xpOrigin},
    {key: 'Access-Control-Allow-Credentials', value: 'true'},
    {key: 'Access-Control-Allow-Methods', value: 'GET, POST, PUT, DELETE, PATCH, OPTIONS'}
  10. Adapter API changes:

    • getContentApiUrl(), getUrl(), getAsset(), and UrlProcessor removed

    • MetaData.apiUrl replaced by branch, project, and site

    • BaseMacro no longer accepts renderInEditMode; RichTextView no longer accepts renderMacroInEditMode, and its customReplacer receives (domNode, data, meta)

    • IS_DEV_MODE follows NODE_ENV; the MODE variable is no longer read

  11. XP configuration: Remove any contentSecurityPolicy.header entries added for Next.XP to com.enonic.app.contentstudio.cfg. The Next.XP app now allows its configured origins automatically, but only explicitly configured URLs in production mode. com.enonic.app.nextxp.cfg keeps the same property format. Site setup is unchanged: the Next.XP app must be added to the XP site with its front-end configuration selected.

Upgrading from v4.x

Complete these steps first, then follow the v5.x steps above.

  1. Adapter upgrade: Update @enonic/nextjs-adapter to ^5.0.0.

    Requires Node.js 24.15.0+ and npm 10.9.2+. The Guillotine query syntax and ENONIC_API URL format are unchanged.

  2. Enonic XP 8: The back-end must run Enonic XP 8, with application schemas in the YAML format under src/main/resources/cms/. Guillotine 8 is used with XP 8 — existing queries work unchanged. Note that media links in rich text now use the /_/media:image/ and /_/media:attachment/ URL format, which requires adapter v5.

Upgrading from v3.x

Complete these steps first, then follow the v4.x and v5.x steps above.

  1. Framework upgrades: Next.js 16 and React 19 are now required.

  2. Adapter upgrade: Update @enonic/nextjs-adapter to ^4.0.0.

    The Guillotine query syntax and ENONIC_API URL format are unchanged.

  3. Middleware replaced by proxy: Rename src/middleware.ts to src/proxy.ts, following the Next.js 16 convention.

    If you have custom middleware logic, migrate it to the new proxy pattern.

  4. Next.js 16 async params: Route params are now Promise and must be awaited:

    // Before (v3.x)
    export type PageProps = {
        locale: string,
        contentPath: string[],
    }
    export default async function Page({params}: { params: PageProps }) {
        const {locale, contentPath} = params;
    
    // After (v4.x)
    export type PageProps = {
        locale: string,
        contentPath?: string[],
    }
    export default async function Page({params}: { params: Promise<PageProps> }) {
        const resolvedParams = await params;
  5. ESLint flat config: Migrate from .eslintrc to eslint.config.mjs:

    import nextVitals from 'eslint-config-next/core-web-vitals'
    import {defineConfig, globalIgnores} from 'eslint/config'
    
    const eslintConfig = defineConfig([
        ...nextVitals,
        {
            rules: {
                "@next/next/no-img-element": "off",
                "eol-last": "warn"
            }
        },
        globalIgnores(['.next/**', 'out/**', 'build/**', 'next-env.d.ts'])
    ])
    
    export default eslintConfig
  6. Default port change: npm run start no longer forces port 4242 — production now runs on the default port 3000.

    If you relied on port 4242, update your com.enonic.app.nextxp.cfg and any reverse-proxy configuration accordingly.

Upgrading from v2.x

  1. You need to use Next.js app router to be able to use v3.x. See upgrade instructions from pages router here.

  2. _renderable page is now an API route at src/api/renderable/route.ts.

  3. @enonic/nextjs-adapter methods renamed:

    • getLocaleProjectConfig() → getLocaleMapping()

    • getProjectLocaleConfigById → getLocaleMappingById()

  4. @enonic/nextjs-adapter method signature changes:

    • fetchFromApi method changed to :

      fetchFromApi(
         apiUrl: string,
         projectConfig: ProjectLocaleConfig,
         options?: FetchOptions): Promise<GuillotineResponseJson>
    • fetchGuillotine method signature changes:

      fetchGuillotine(
         contentApiUrl: string,
         projectConfig: ProjectLocaleConfig,
         options?: FetchOptions): Promise<GuillotineResult>
  5. @enonic/nextjs-adapter exports rearranged:

    Exports working both on the server and client sides are now available from @enonic/nextjs-adapter package root. Client-only exports are available from @enonic/nextjs-adapter/client package. Server-only exports are available from @enonic/nextjs-adapter/server package. Views are available at @enonic/nextjs-adapter/views/<ViewName>.

  6. ENONIC_PROJECTS environmental variable change:

    ENONIC_PROJECTS -> ENONIC_MAPPINGS
    Language is now required for every project config:
    ENONIC_MAPPINGS=<lang>:<repo-name>/site/path[,<lang>:<repo-name>/site/path,...]
    First config in the list is used as default.
  7. next.config.js config changes:

    Remove i18n block from next.js config:
    i18n: {
        locales: ['en', 'no'],
        defaultLocale: 'en',
    }
    Add phrases block to webpack config:
    config.resolve.alias = {
        ...config.resolve.alias,
        "@phrases": path.resolve(__dirname, "./src/phrases"),
    }
  8. Add src/phrases folder to your project root. This folder should contain static translations for every language from ENONIC_MAPPINGS env var in the following format: src/phrases/<lang>.json

    src/phrases/en.json
    src/phrases/no.json
  9. Middleware file has been updated to support new i18n config. If you have custom proxy, you need to update it to support new i18n config.


Contents

Contents