The following worked for me :
To get the content from the database, I used async asyncData, and as you can access context, redirect, req, and res as parameters you can perform a 301 redirect.
In my folder, I used the unknown dynamic nested routes like this: /pages/folder/_.vue. Which will catch all routes including domain.com/folder/{id} and domain.com/folder/{id}/{title}.
Split the pathMatch and get the first element, like this params.pathMatch.split('/')[0] to get the ID of in request URL. Then I use the id to get the content from the database, which is Strapi in my case. Like this await $strapi.findOne('contentType', id).
Then like this : /folder/${data.id}/${data.slug} create the actual URL . Now you can match the requested URL with the actual URL.
My entire code:
async asyncData({ redirect, req, route, app, $strapi, error, params }) {
try {
const recipeID = params.pathMatch.split('/')[0];
const matchingRecipe = await $strapi.findOne('recipes', recipeID);
const correctPath = `${app.localePath('recipes')}/${matchingRecipe.id}/${matchingRecipe.slug}`;
if(route.fullPath !== correctPath) {
console.log(`Redirect: ${route.fullPath} => ${correctPath}`);
redirect(301, correctPath);
}
return {
recipe: matchingRecipe
}
} catch(e) {
console.log(e)
error({ statusCode: e.statusCode, message: e.original });
}
},