These OOB comments in the render.js/render.ts file provide additional context:
/**
* This Next.js API route is used to handle POST requests from the Sitecore Experience Editor.
* This route should match the `serverSideRenderingEngineEndpointUrl` in your Sitecore configuration,
* which is set to "http://localhost:3000/api/editing/render" by default (see \sitecore\config\JssNextWeb.config).
*
* The `EditingRenderMiddleware` will
* 1. Extract editing data from the Experience Editor POST request
* 2. Stash this data (for later use in the page render request) via the `EditingDataService`, which returns a key for retrieval
* 3. Enable Next.js Preview Mode, passing our stashed editing data key as preview data
* 4. Invoke the actual page render request, passing along the Preview Mode cookies.
* This allows retrieval of the editing data in preview context (via the `EditingDataService`) - see `SitecorePagePropsFactory`
* 5. Return the rendered page HTML to the Sitecore Experience Editor
*/
// Bump body size limit (1mb by default) for Experience Editor payload
// See https://nextjs.org/docs/api-routes/api-middlewares#custom-config
As the error message suggests, editing data is unable to be extracted from the request. Some scenarios in which you may see this error are:
bodyParser
middleware is not enabled on the route
- You have hit the
sizeLimit
defined in your bodyParser
config
- The POST request lacks a request body entirely (
undefined
) -- this will 100% trigger the error described
- The POST request body is malformed
In order to troubleshoot, check your Next logs for more error details. You may see something like:
Error: Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.
at extractEditingData (C:\projects\MySite\node_modules\@sitecore-jss\sitecore-jss-nextjs\dist\cjs\middleware\editing-render-middleware.js:174:15)
at EditingRenderMiddleware.<anonymous> (C:\projects\MySite\node_modules\@sitecore-jss\sitecore-jss-nextjs\dist\cjs\middleware\editing-render-middleware.js:54:37)
at Generator.next (<anonymous>)
at C:\projects\MySite\node_modules\@sitecore-jss\sitecore-jss-nextjs\dist\cjs\middleware\editing-render-middleware.js:8:71
at new Promise (<anonymous>)
at __awaiter (C:\projects\MySite\node_modules\@sitecore-jss\sitecore-jss-nextjs\dist\cjs\middleware\editing-render-middleware.js:4:12)
at EditingRenderMiddleware.handler (C:\projects\MySite\node_modules\@sitecore-jss\sitecore-jss-nextjs\dist\cjs\middleware\editing-render-middleware.js:28:38)
at Object.apiResolver (C:\projects\MySite\node_modules\next\dist\server\api-utils\node.js:366:15)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async DevServer.runApi (C:\projects\MySite\node_modules\next\dist\server\next-server.js:481:9)
You can also print the request handler to the console like so:
export const config = {
api: {
bodyParser: {
sizeLimit: '20mb',
},
},
}
// Wire up the EditingRenderMiddleware handler
const handler = new EditingRenderMiddleware().getHandler();
console.log(`***HANDLER*** ${handler}`)
Output:
(req, res) => __awaiter(this, void 0, void 0, function* () {
var _e;
const { method, query, body, headers } = req;
sitecore_jss_1.debug.experienceEditor('editing render middleware start: %o', {
method,
query,
headers,
body,
});
if (method !== 'POST') {
sitecore_jss_1.debug.experienceEditor('invalid method - sent %s expected POST', method);
res.setHeader('Allow', 'POST');
return res.status(405).json({
html: `<html><body>Invalid request method '${method}'</body></html>`,
});
}
// Validate secret
const secret = (_e = query[editing_data_service_2.QUERY_PARAM_EDITING_SECRET]) !== null && _e !== void 0 ?
_e : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
if (secret !== utils_1.getJssEditingSecret()) {
sitecore_jss_1.debug.experienceEditor('invalid editing secret - sent "%s" expected "%s"', secret, utils_1.getJssEditingSecret());
return res.status(401).json({
html: '<html><body>Missing or invalid secret</body></html>',
});
}
try {
// Extract data from EE payload
const editingData = extractEditingData(req);
// Resolve server URL
const serverUrl = this.resolveServerUrl(req);
// Stash for use later on (i.e. within getStatic/ServerSideProps).
// This ultimately gets stored on disk (using our EditingDataDiskCache) for compatibility with Vercel Serverless Functions.
// Note we can't set this directly in setPreviewData since it's stored as a cookie (2KB limit)
// https://nextjs.org/docs/advanced-features/preview-mode#previewdata-size-limits)
const previewData = yield this.editingDataService.setEditingData(editingData, serverUrl);
// Enable Next.js Preview Mode, passing our preview data (i.e. editingData cache key)
res.setPreviewData(previewData);
// Grab the Next.js preview cookies to send on to the render request
const cookies = res.getHeader('Set-Cookie');
// Make actual render request for page route, passing on preview cookies.
// Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed
to do it)
const requestUrl = this.resolvePageUrl(serverUrl, editingData.path);
sitecore_jss_1.debug.experienceEditor('fetching page route for %s', editingData.path);
const pageRes = yield this.dataFetcher
.get(`${requestUrl}?timestamp=${Date.now()}`, {
headers: {
Cookie: cookies.join(';'),
},
})
.catch((err) => {
// We need to handle not found error provided by Vercel
// for `fallback: false` pages
if (err.response.status === 404) {
return err.response;
}
throw err;
});
let html = pageRes.data;
if (!html || html.length === 0) {
throw new Error(`Failed to render html for ${requestUrl}`);
}
// replace phkey attribute with key attribute so that newly added renderings
// show correct placeholders, so save and refresh won't be needed after adding each rendering
html = html.replace(new RegExp('phkey', 'g'), 'key');
// When SSG, Next will attempt to perform a router.replace on the client-side to inject the query string parms
// to the router state. See https://github.com/vercel/next.js/blob/v10.0.3/packages/next/client/index.tsx#L169.
// However, this doesn't really work since at this point we're in the editor and the location.search has nothing
// to do with the Next route/page we've rendered. Beyond the extraneous request, this can result in a 404 with
// certain route configurations (e.g. multiple catch-all routes).
// The following line will trick it into thinking we're SSR, thus avoiding any router.replace.
html = html.replace(constants_1.STATIC_PROPS_ID, constants_1.SERVER_PROPS_ID);
const body = { html };
// Return expected JSON result
sitecore_jss_1.debug.experienceEditor('editing render middleware end: %o', { status: 200, body });
res.status(200).json(body);
}
catch (error) {
console.error(error);
if (error.response || error.request) {
// Axios error, which could mean the server or page URL isn't quite right, so provide a more helpful hint
console.info(
// eslint-disable-next-line quotes
"Hint: for non-standard server or Next.js route configurations, you may need to override the 'resolveServerUrl' or 'resolvePageUrl' available on the 'EditingRenderMiddleware' config.");
}
res.status(500).json({
html: `<html><body>${error}</body></html>`,
});
}
})
In summary, it looks like you are hitting this in the handler as a result of an error during the call to extractEditingData()
:
res.status(500).json({
html: `<html><body>${error}</body></html>`,
);
Your specific issue may vary, so use the above as a guide to investigate further.