vue.jshttp-redirectnuxt.jsseofriendly-url

How to get Stack Overflow SEO friendly URL structure in Nuxt.js?


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.


Solution

  • Nuxt 2

    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

    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.