Update the new JSS 22.12 Next.js template files in your existing app

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:
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;
},
});
}; Next steps
To finalize the upgrade process, make sure you resolve any errors and warnings you encounter. Enable debug logging for JSS specific issues to assist you if necessary.
