Development / CMS
Update the new JSS 22.12 Next.js template files in your existing app
SSaurav Kumar1 Min Read

This explains how to synchronize files in your existing application with corresponding files from your new JSS 22.12 app.
To update the Next.js template files:
Middleware has been renamed to proxyâ¯to better reflect its purpose. Renameâ¯src/middleware.tsâ¯toâ¯src/proxy.ts. To update your implementation:
Modify its export function so that it no longer accepts theâ¯evâ¯parameter:
Ensure a Smooth, Error-Free Migration to the Latest JSS Version
typescript
MSI Editor
export default async function proxy(req: NextRequest) {
return middleware(req);
}
Modify method signatures inâ¯/src/lib/middleware/index.tsâ¯so that it no longer usesâ¯NextFetchEvent:
export interface MiddlewarePlugin {
/**
* Detect order when the plugin should be called, e.g. 0 - will be called first (can be a plugin which data is required for other plugins)
*/
order: number;
/**
* A middleware to be called, it's required to return @type {NextResponse} for other middlewares
*/
exec(req: NextRequest, res?: NextResponse): Promise<NextResponse>;
}
...
export default async function middleware(
req: NextRequest
): Promise<NextResponse> {
...
const finalRes = await (Object.values(plugins) as MiddlewarePlugin[])
.sort((p1, p2) => p1.order - p2.order)
.reduce((p, plugin) => p.then((res) => plugin.exec(req, res)), Promise.resolve(response));
...
...
Remove all imports ofâ¯NextFetchEvent.
By disabling React Suspense in placeholders, components render faster without the added Suspense overhead. However, if your app uses lazy-loaded components inside aâ¯<Placeholder/>â¯and expects a loading fallback to appear, you need to perform the following steps while upgrading:
Auditâ¯<Placeholder/>â¯usages where dynamic or lazy components are rendered.
Addâ¯disableSuspense={false}â¯to any placeholder that depends on Suspense-based loading states.
Update the XM Cloud add-on
To update the XM Cloud add-on:
In theâ¯FEAASScripts.tsâ¯file:
Find theâ¯convertToRegexâ¯function:
const convertToRegex = (pattern: string) => {
return pattern.replace('.', '\\.').replace(/\*/g, '.*');
};
Replace it with the following:
const convertToRegex = (pattern: string) => {
return pattern.replace(/\\/g, '\\\\').replace(/\./g, '\\.').replace(/\*/g, '.*');
};
Update the SXA add-on
To update the SXA add-on:
Remove theâ¯sass-aliasâ¯dependency:
"sass-alias": "^1.0.5"
Modifyâ¯/src/lib/next-config/plugins/sass.js:
Find all theâ¯sass-aliasâ¯imports:
const SassAlias = require('sass-alias');
Replace it with the following:
const fs = require('fs');
const { pathToFileURL, fileURLToPath } = require('url');
Add a new function calledâ¯createAliasImporter:
/**
* Custom Sass importer compatible with Next.js 16's Sass API
* Implements canonicalize and load methods as required by the new Sass JS API
*/
function createAliasImporter(aliases) {
return {
canonicalize(url, context) {
// Check if the URL matches any of our aliases
for (const [alias, aliasPath] of Object.entries(aliases)) {
if (url.startsWith(alias)) {
// Remove the alias prefix and resolve the path
const relativePath = url.slice(alias.length);
// Remove leading slash if present
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
const fullPath = path.resolve(aliasPath, cleanPath);
// Try to find the file with common extensions
const extensions = ['.scss', '.sass', '.css'];
for (const ext of extensions) {
const filePath = fullPath + ext;
if (fs.existsSync(filePath)) {
return pathToFileURL(filePath);
}
}
// Try as a directory with index file
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
for (const ext of extensions) {
const indexPath = path.join(fullPath, 'index' + ext);
if (fs.existsSync(indexPath)) {
return pathToFileURL(indexPath);
}
}
}
// Try with underscore prefix (Sass partials)
const dir = path.dirname(fullPath);
const basename = path.basename(fullPath);
for (const ext of extensions) {
const partialPath = path.join(dir, '_' + basename + ext);
if (fs.existsSync(partialPath)) {
return pathToFileURL(partialPath);
}
}
// Return the path even if file doesn't exist yet, let Sass handle the error
return pathToFileURL(fullPath);
}
}
// Return null if no alias matches, let other importers handle it
return null;
},
load(canonicalUrl) {
if (canonicalUrl.protocol === 'file:') {
// Convert file:// URL back to file path
const filePath = fileURLToPath(canonicalUrl);
if (fs.existsSync(filePath)) {
const contents = fs.readFileSync(filePath, 'utf8');
// Determine syntax based on file extension
const syntax = filePath.endsWith('.sass') ? 'indented' : 'scss';
return {
contents,
syntax,
};
}
}
return null;
},
};
}
Modify theâ¯sassPluginâ¯export to use the new implementation:
const sassPlugin = (nextConfig = {}) => {
const aliases = {
'@sass': path.join(__dirname, '../../../assets', 'sass'),
'@fontawesome': path.join(__dirname, '../../../../node_modules', 'font-awesome'),
};
return Object.assign({}, nextConfig, {
sassOptions: {
importers: [createAliasImporter(aliases)],
// temporary measure until new versions of bootstrap and font-awesome released
quietDeps: true,
silenceDeprecations: ["import", "legacy-js-api"],
},
webpack: (config, options) => {
// Exclude Node.js built-in modules used by this plugin from client bundle
if (!options.isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
url: false,
};
}
// Call existing webpack config if present
if (typeof nextConfig.webpack === 'function') {
return nextConfig.webpack(config, options);
}
return config;
},
});
}; 

