CMS + Next.js: Migration
Update an older website integration to the current erxes API. This is an application-integration migration, not a database migration or a guarantee that every older deployment exposes the same schema.
1. Inventory the current integration
Record the deployed erxes revision, website dependency versions, gateway URL, credential headers, GraphQL operations, and CMS/portal associations. Identify which requests run in the browser and which already run on the server.
Distinguish public website reads from authenticated administration, previews, portal-user actions, and mutations. They may require different credentials and authorization.
2. Move credentials into server modules
Follow Setup. Remove credential values from next.config.env, NEXT_PUBLIC_* variables, static configuration files, and client components. Rebuild the website to remove those values from newly generated browser assets.
The public examples use a Client Portal token through x-app-token. Existing administrative integrations may continue to require an Apps token through erxes-app-token; keep those behind their own authorized server interface.
If an Apps credential was published in browser assets, treat it as exposed and coordinate revocation and replacement through the application's administration. The portal JWT path verifies the signature and portal existence but does not compare the submitted JWT with the portal's stored token (ClientPortal.token). Changing ClientPortal.token alone is therefore not proof that older JWTs are invalid.
3. Update operation names and shapes
Compare each operation with Queries and the schema for your installed version.
Common corrections:
- Use
categoryIds: [String]for post filters, rather thancategoryId. - Most list operations use
limitandcursor. The dedicatedcpPostListWithPaginationoperation acceptspageandperPage. - A post list envelope contains
posts; a page list containspages; a category list containslist; a tag list containstags. - The post detail field is
cpPost; there is nocpPostDetail. - The page detail field is
cpCmsPageDetail. - A menu item's field is
contentTypeId, with a lowercase finald. - The
Authorunion containsUser; do not add an unsupportedClientPortalUserfragment.
The schema directory defines these operations and fields. A nullable schema argument may still be required by a resolver, so inspect runtime behavior as well.
4. Enforce publication policy
Switching an operation from cms* to cp* does not automatically hide draft content. Post lists apply the requested status, while post detail reads do not add a published-only filter.
Use a fixed status: published filter for public post lists. Verify the returned status before exposing a detail record or using its fields in metadata. Check pages, menus, categories, and other content families individually; do not infer their policy from the post API.
The SSR guide demonstrates published-only list and detail handling. The CSR guide exposes a fixed website search endpoint with bounded inputs.
5. Rendering and caching
Keep server credential modules outside the browser dependency graph. A component's imports become part of the client graph when reached through a client boundary, even if each imported file does not declare "use client".
The Setup helper uses cache: "no-store". Decide on a deliberate caching policy before adding revalidation settings; a page-level revalidation declaration does not override an explicitly uncached fetch.
The cpPost detail resolver increments a view counter. Repeated metadata and page queries can increase it, and cached results can reduce counted reads. Verify analytics expectations separately from content rendering.
Render stored content according to its format. Sanitize untrusted HTML before using an HTML insertion API, and preserve the application's image and link handling.
6. Test before switching traffic
Test these cases against a staging instance with known records:
- Published and draft posts, including direct detail URLs and metadata.
- Unknown slugs and an empty portal.
- Cursor boundaries, filters, and expected response envelopes.
- Missing/invalid credentials and unavailable upstream services.
- Browser requests and built assets without erxes credentials.
- Rejected unsupported parameters and operations.
- Authenticated previews or mutations through their separate authorized flows.
- Image URLs and rich content rendering.
If introspection is enabled in the development instance, compare the live schema with the query documents. Otherwise use the installed source schema or an administrator-provided export. A production deployment may intentionally disable introspection.
Keep the previous website build and configuration available during rollout. Restoring a website build does not reverse product database migrations; handle a product upgrade according to its own migration procedure.
Troubleshooting
- 401/403 or GraphQL auth errors after migration: the wrong credential family is being sent; the website needs the Client Portal token in
x-app-token, not an Apps token, and not aclient-auth-token. - Valid token, empty lists: the portal matched but has no matching records; re-check
clientPortalIdon posts, pages, and menus after a portal change. Unknown argumenterrors:page/perPageonly exist oncpPostListWithPagination; other lists takelimit/cursor. Check the Queries reference.- Drafts visible on public routes: a
cp*prefix does not filter publication status; enforcestatus: publishedon lists and checkstatuson detail reads. - Stale content after deployment: the Setup helper uses
cache: "no-store"; if you added caching, re-checkrevalidatesettings and any CDN layer.