Gatsby: How can I generate URL of my Gatsby blog post automatically like “blog/{ID}”?

Created on 2 Sep 2019  ·  10Comments  ·  Source: gatsbyjs/gatsby

Summary

By default my Gatsby urls are like blog/slug.
Is there any way that they could become blog/{post-id}?
I usually use Japanese to write articles so urls become too long when encoded...

For example,
https://www.cantas.co.jp/blog/2019-08-28-%E4%B8%AD%E5%B0%8F%E4%BC%81%E6%A5%AD%E3%81%8C%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%88%E3%82%8Aslack%E3%82%92%E4%BD%BF%E3%81%86%E3%81%B9%E3%81%8D10%E3%81%AE%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88/

What's the best approach to implement this?

awaiting author response question or discussion

All 10 comments

you could use the internal índex as an alternative, by modifying the query to include the internal id, or if you want, when you're iterating over the edges array to generate the pages, assuming by using a foreach it allows the índex argument that can be used. If you don't mind waiting I can post a code example a little later, as it's 3 Am where i'm at And I need to be up in 6 hours. or you could check the official blog starter to see how to use a foreach with a index

Thank you very much @jonniebigodes!

I implemented my gatsby-node.js using edge.node.id and urls became like this:
http://localhost:8000/blog/a6be647d-239c-5299-896d-18f7b93ba210

Its still too long url to share...
Would be great if you could share a better way 😊

   const posts = result.data.allMarkdownRemark.edges;
   posts.forEach(edge => {
      const id = edge.node.id;
      createPage({
        path: '/blog/' + id,
        tags: edge.node.frontmatter.tags,
        component: path.resolve(
          `src/templates/${String(edge.node.frontmatter.templateKey)}.jsx`
        ),
        context: {
          id
        }
      });
    });

@shihokambara like i said before you can iterate over the edges like you're doing but with the index added.
Below is a example on how to achieve the same result. It's a bit diferent of your code.

exports.createPages = async ({ actions, graphql }) => {
  const { createPage } = actions
  const dummyMarkdownData = await graphql(`
    {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              title
              path
            }
          }
        }
      }
    }
  `)
  if (dummyMarkdownData.errors) {
    throw dummyMarkdownData.errors
  }

  dummyMarkdownData.data.allMarkdownRemark.edges.forEach((edge, index) => {
    createPage({
      path:`/blog/${index}/`, //<- will create page like /blog/0 /blog/1
      component: require.resolve(`./src/templates/dummy-template.js`),
      context: {
        title: edge.node.frontmatter.title,
      },
    })
  })
}

After you adjust the code to your needs in gatsby-node.js and run gatsby develop it will result in the pages being created like http://localhost:8000/blog/0/, http://localhost:8000/blog/1/ to http://localhost:8000/blog/n/, where n is the number of posts you have created.
It's a shorter route as you can see and probably more manageable to you.

Also if you have a page where all the posts are listed, you'll probably have to adjust it as well.

Thank you again, @jonniebigodes!
However, if I use simple index to create path and then delete some articles, index number & urls will change.
For example, if I delete blog/3, url of blog/4 becomes blog/3.
This is not good for SEO, I think.

You may consider this hash function. It was taken from here. Its output is relatively short (9-10 characters) and consistent, thus would likely meet your requirements.

function hashString (str) {
  let hash = 0

  if (str.length === 0) {
    return hash
  }

  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i)
    hash = (hash << 5) - hash + char
    hash = hash & hash
  }

  return hash
}

Then generate your path like so

{
  ...
  path: `/blog/${makeHash(edge.node.frontmatter.title)}`
  ...
}

One caveat is sometimes it will generates hash starting with - e.g. -2145214523. It's up to you how you want to work around that.

-- edit: my bad, I think it's preferable to makeHash(id) instead of title for consistency purposes, as you may change post title in the future.

@universse basically it will lead to the same situation that was proposed earlier, by using the id,

@shihokambara i understand it perfectly, I don't know how big is your blog is going to grow, or what your plans are for it. But if you intend to keep it small, you can use a random generated number to keep it contained.
Something like this added to your gatsby-node.js:

const randomizeNumber = () => {
  return Math.floor(Math.random() * (999999 - 1 + 1) + 1)
}
exports.createPages = async ({ actions, graphql }) => {
......
 const posts = result.data.allMarkdownRemark.edges;
   posts.forEach(edge => {

      createPage({
        path: `/blog/${randomizeNumber()}/`,
        tags: edge.node.frontmatter.tags,
        component: path.resolve(
          `src/templates/${String(edge.node.frontmatter.templateKey)}.jsx`
        ),
        context: {
          id
        }
      });
    });
}
````
But if you plan on going big and keep the blog going i would take the shortid url approach, use something like [this](https://www.npmjs.com/package/shortid) to generate simple short urls that are friendly.

With that you adjust your `gatsby-node.js` to the following:
```js
const shortid = require('shortid');
exports.createPages = async ({ actions, graphql }) => {
......
 const posts = result.data.allMarkdownRemark.edges;
   posts.forEach(edge => {

      createPage({
        path: `/blog/${shortid.generate()}/`,
        tags: edge.node.frontmatter.tags,
        component: path.resolve(
          `src/templates/${String(edge.node.frontmatter.templateKey)}.jsx`
        ),
        context: {
          id
        }
      });
    });
}

When the code runs it wil generate something like this:

  • http://localhost:8000/blog/a68jxT63p/
  • http://localhost:8000/blog/pIBL5bw6tL/
  • http://localhost:8000/blog/bq9RIi5tnJ/

First, as stated, the output of makeHash is relatively short (9-10 characters).
Second, with your approach, every time you build your site, a new url is generated for each post, which is undesirable.

First, as stated, the output of makeHash is relatively short (9-10 characters).

That's why i sugested the random number generated.

Second, with your approach, every time you build your site, a new url is generated for each post, which is undesirable.

With my approach or yours it will always generate a new url at each build. The way to prevent this is to have the content not hosted locally in markdown, but for instance something database hosted like wordpress. Or other providers like sanity, or contentful.

That's why i sugested the random number generated.

So you are suggesting the same thing?

With my approach or yours it will always generate a new url at each build.

For my approach, hashString take post's id as parameter. hashString is pure so basically same input same output. And I'm presuming post's id is the same between builds.

-- edit:

post's id is the same between builds

I just tested things out and this might not be the case all the time.
So I guess the solution would be having a field like createdAt in your markdown.

---
title: Some title
createdAt: "2015-05-28T22:40:32.169Z"
---

Since createdAt is likely to be consistent and unique, using hashString(createdAt) would meet the requirements.

As others have mentioned here already, you can use createPage (and more specifically the path arg there) to create your custom paths. Hence I'm closing this issue as answered. If you have follow-up questions please reply here.

You typically use the title or date + title for the URL and I'd advise to use that here, too!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

benstr picture benstr  ·  3Comments

3CordGuy picture 3CordGuy  ·  3Comments

mikestopcontinues picture mikestopcontinues  ·  3Comments

jimfilippou picture jimfilippou  ·  3Comments

rossPatton picture rossPatton  ·  3Comments