Stack Overflow has the following URL structure stackoverflow.com/questions/{question ID}/{question title}
, and if you misstype the {question title}
you will be permanently redirected 301
to the correct URL as long as you have the correct {question ID}
.
Assuming that I have both the id
and the slug
, how can I make the same URL structure that Stack Overflow has in Nuxt.js
with SSR
?
Edit: Apprently the URL structure is known as Clean URL.
I have tried using a fully dynamic URLs with pages/x/_.vue
, but that gives me a "new" page for each request and does not give a 301 redirect.
This post suggest the following: /questions/8811192/question-title
could be rewritten to /mywebsite.php?id=8811192&convention=questions
. So if I can interpret /qeustions/{id}/{title}
as just /qeustions/{id}
I could be halfway there I guess.
The following works for me, but I am not sure if it's the exact same as how the Stack Overflow URL structure works.
I am using async asyncData
to get content from the database, and as you can access
context and redirect
, req
and res
as parameters you can perform a 301
redirect.
First I am using unknown dynamic nested routes in my folder like this /pages/folder/_.vue
. Which will catch all routes including domain.com/folder/{id}
and domain.com/folder/{id}/{title}
.
To get the ID of in the requested url you can split the pathMatch and get the first element, like this params.pathMatch.split('/')[0]
.
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)
.
After that we can create the actual URL we want like this /folder/${data.id}/${data.slug}
. Note: the data.slug
culd be replaced with the data.title
which could be converted to a URL friendly string.
Finally we can match the user requested URL with the actual URL of the content if(route.fullPath !== actualURL)
, and if the requested url is not the same we can performe a redirect with redirect(301, actualURL)
.
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 });
}
},
For Nuxt 3 I have an composable called useRedirect
, which looks like the following:
> composables > useRedirect.ts
import type { AnyRecordResponse, RecordTypes } from '~~/types/strapi';
export default function (response: AnyRecordResponse, recordType: RecordTypes) {
if (!response || !recordType) {
showError({ statusCode: 404, statusMessage: 'Page Not Found' });
return;
}
const { path, query } = useRoute();
const correctPath = useGetURL(response.data, recordType, false);
// Redirect page if URL is wrong.
if (correctPath !== path) {
navigateTo({
path: correctPath,
query: { ...query }
},
{ redirectCode: 301 });
}
return correctPath;
}
To get domain.com/folder/{id}/{title}
in Nuxt 3 your folder structure should look like this:
> pages > SOMETHING > [id] > [[slug]].vue
Double brackets around slugg makes it optional, so you can reach the page with only the id like this domain.com/folder/{id}
.
The composable useRedirect
you should call on the page where you want to redirect.