Bulk redirect as partial

Hello!

I’m in the process of adding redirect aliases to about 40 of my posts, and I’m curious if this can be done programmatically instead. All of the posts follow this format:
old URL: /blog/posts/this-post-url/
new URL: /blog/this-post-url/

My first thought was to use a partial, but since that references the front-matter, I don’t believe it will be able to help generate front-matter (or act in lieu of it). Of course, I’m happy to be proven wrong.

I didn’t see another way of creating redirects aside from aliases in the docs or a similar post to this question – but if those things exist, please let me know! Thanks for reading!

Bulk redirects can ben operated by your host. If you’re using Netlify you can generate the _redirects file they use through Hugo (and custom output formats) and therefor make it as dynamic as you like. We built a module that do just that:

You could also add the aliases with a quick-n-dirty script. See below.

Warning

Make sure your site is backed up, or initialized as a git repo before running this.

How it Works

  • Recursively walk the content dir and find files ending in .md
  • For each file, build the desired alias, and replace the 2nd occurrence of --- with the new aliases frontmatter

Sample Output

$ node task_add_aliases_to_frontmatter.js 
Editing file: content/blog/this-post-url/index.md
Adding alias: /blog/posts/this-post-url/
Editing file: content/blog/this-post-url-2.md
Adding alias: /blog/posts/this-post-url-2/

The Script

const fs = require('fs');
const path = require('path');

const DIR = 'content';
let MATCH_COUNTER = 0;

const walk = (dir) => {
  try {
    let results = [];
    const list = fs.readdirSync(dir);
    list.forEach((file) => {
      file = path.join(dir, file);
      const stat = fs.statSync(file);
      if (stat && stat.isDirectory()) {
        // Recurse into subdir
        results = [...results, ...walk(file)];
      } else {
        // Is a file
        results.push(file);
      }
    });
    return results;
  } catch (error) {
    console.error(`Error when walking dir ${dir}`, error);
  }
};

const edit = (filePath) => {
  console.log(`Editing file: ${filePath}`);

  const oldContent = fs.readFileSync(filePath, {encoding: 'utf8'});
  const regex = /---/g;

  const split = filePath.split('/');
  let filename = null;

  if (split[split.length - 1] === 'index.md') {
    filename = split[split.length - 2];
  } else {
    filename = split[split.length - 1].replace(/.md$/, '');
  }

  const alias = `/blog/posts/${filename}/`;
  console.log(`Adding alias: ${alias}`);

  const replaceVal = `\
aliases:
  - ${alias}
---`;

  const replacer = (match) => {
    MATCH_COUNTER++;
    if (MATCH_COUNTER === 2) {
      return replaceVal;
    }
    return match;
  };

  const newContent = oldContent.replace(regex, replacer);
  fs.writeFileSync(filePath, newContent, {encoding: 'utf-8'});

  MATCH_COUNTER = 0;
};

const main = () => {
  const filePaths = walk(DIR).filter((filePath) => filePath.endsWith('.md'));
  filePaths.forEach((filePath) => edit(filePath));
};

main();
2 Likes

Yes, I knew this was possible – I don’t know why I didn’t really consider it as an option. But your response prompted me to try it! I use firebase hosting, so to implement this all I did was add this to my firebase.json file:

{
    "hosting": {
       ...
        "redirects": [
            {
                "source": "/blog/posts/:slug",
                "destination": "/blog/:slug",
                "type": 301
      }
    ]
    }
...
}

It worked like a charm.

Here are the relevant links I used (in case someone comes across this post):

https://firebase.google.com/docs/hosting/full-config#redirects

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.