CMS + Next.js: Setup
Connect a Next.js App Router website to erxes using a server-side Client Portal credential. Complete these steps before the SSR and CSR examples.
1. Prepare the erxes instance
Enable the Content plugin and create a Client Portal for the website using an account with the required administrative access. Confirm the website's CMS records belong to that portal. Interface labels vary by release; the backend stores the credential as ClientPortal.token.
Use your deployment's gateway URL. A hosted installation commonly exposes https://YOUR_TENANT.app.erxes.io/gateway/graphql; the local setup guide uses http://localhost:4000/graphql. A self-hosted proxy may expose another prefix.
Create a published test post with a title and slug. Also create a draft so you can verify that it is excluded from the website.
2. Obtain the portal token
Copy the token from the intended Client Portal using your authorized administration interface. Send it as x-app-token. This is different from an Apps token sent as erxes-app-token.
The token manager signs a JWT containing the portal ID. The gateway verifies its signature and loads that portal.
The clientPortalChangeToken implementation stores a newly signed token, but gateway verification does not compare incoming JWTs with that stored token. Do not assume changing the stored token invalidates previously issued JWTs. Coordinate credential invalidation with the deployment owner if a token was exposed.
3. Configure the website
Add these variables to the website's private local environment file or hosting environment:
ERXES_API_URL=https://YOUR_TENANT.app.erxes.io/gateway/graphql
ERXES_APP_TOKEN=REPLACE_WITH_CLIENT_PORTAL_TOKEN
ERXES_FILE_URL=https://YOUR_TENANT.app.erxes.io/gateway/read-file?key=
Here ERXES_APP_TOKEN is a website variable name for the Client Portal token; it is not the administrative Apps credential. Keep the private file out of version control and put empty placeholders in the project's example environment file.
Do not add these credentials to next.config's env object or prefix them with NEXT_PUBLIC_. Use server-only to detect accidental imports into a client component's dependency graph:
npm install server-only
Use the equivalent command if your website uses another package manager. The examples use native fetch, so Apollo Client is optional. Existing Apollo applications can use the same GraphQL documents and credential boundaries.
4. Add the server fetch helper
Create this module in the website, not in the erxes product repository:
// lib/erxes/server.ts
import "server-only";
type GraphQLResponse<T> = {
data?: T | null;
errors?: Array<{ message: string }>;
};
export async function erxesQuery<T>(
query: string,
variables: Record<string, unknown> = {},
): Promise<T> {
const apiUrl = process.env.ERXES_API_URL;
const token = process.env.ERXES_APP_TOKEN;
if (!apiUrl || !token) {
throw new Error("CMS server configuration is missing");
}
const response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-app-token": token,
},
body: JSON.stringify({ query, variables }),
cache: "no-store",
signal: AbortSignal.timeout(10000),
});
if (!response.ok) {
throw new Error(`CMS HTTP request failed (${response.status})`);
}
const result = (await response.json()) as GraphQLResponse<T>;
if (result.errors?.length || result.data == null) {
throw new Error("CMS returned a GraphQL error or no data");
}
return result.data;
}
The helper checks HTTP and GraphQL failures separately. It uses a fixed server-configured URL and credential, and disables caching. The generic type describes the expected response; it is not runtime schema validation.
Do not expose this generic helper through an endpoint that accepts arbitrary client-supplied queries. The CSR guide provides a fixed operation with limited inputs.
5. Build file URLs
Attachment URLs may already be complete URLs or may be storage keys. A website helper can make this distinction:
// lib/erxes/file-url.ts
import "server-only";
export function erxesFileUrl(value?: string | null): string {
if (!value) return "";
if (/^https?:\/\//i.test(value)) return value;
if (value.startsWith("/")) return value;
const base = process.env.ERXES_FILE_URL;
if (!base) throw new Error("CMS file endpoint is missing");
const url = new URL(base);
url.searchParams.set("key", value);
url.searchParams.set("inline", "true");
return url.toString();
}
Confirm whether relative paths in your stored content belong to the website or the gateway; this helper preserves them. Pass the resulting URLs as props to browser components.
The file route accepts a file key and options such as inline and width. If you use next/image, configure its remote image patterns for the actual asset hosts and paths, which can include storage/CDN hosts beyond the gateway.
6. Check it works
- Fetch the published test post with the SSR example; it renders, and the draft does not appear.
- Send an incorrect token; the request errors.
- In the CSR example, inspect browser requests: they reach the website's
/api/postsendpoint and carry no erxes credential.
Empty response or browser CORS errors
An empty successful response usually means the records are not associated to the portal or not published; check that before changing the query. Browser CORS errors mean browser code is calling the gateway directly; the examples route through the website's own endpoint.
For Next.js server/client boundaries, see the official documentation.