Content: How to make slugs follow file structure?

Created on 30 Jun 2020  路  7Comments  路  Source: nuxt/content

I'm trying to make the paths (slugs? I get confused over terminology sometimes) of my content follow the file structure under the /content directory, but I can't get it quite right. You can see my code at https://codesandbox.io/s/uy5lo?file=/nuxt.config.js. This is built on Nuxt 2.13 with target:static.

This is the behavior I hope to achieve:

directory/file  |  resulting path
----------------+----------------
/content        |  
  /hello.md     |  /hello
  /foo.md       |  /foo
  /foo          |
    /bar.md     |  /foo/bar

...or alternatively, this would be fine too:

directory/file  |  resulting path
----------------+----------------
/content        |  
  /hello.md     |  /hello
  /foo          |
    /index.md   |  /foo
    /bar.md     |  /foo/bar

To make this work, I created a _.vue route which fetches content with the {deep: true}. This seems to work fine. I also added the following hook:

  hooks: {
    'content:file:beforeInsert': (document) => {
      // Just strip the leading slash off document.path.
      document.slug = document.path.replace(/^\//, '')
    }
  }

While this works for /hello and /foo/bar, it does not work for /foo. The foo.md file seems to clash with the directory of the same name. I did read in the documentation that "If path [...] is a directory it will return an Array", which sounds like an explanation, but it doesn't provide a solution.

What did I miss? Thanks for your assistance.

question

Most helpful comment

@marcvangend The first argument of $content can actually be nothing because it defaults to /: https://content.nuxtjs.org/fetching#contentpath-options, so you can do:

await $content({ deep: true }).where({ path: `/${path}` }).fetch()

I agree with you, you should be able to query a file with the same name as a directory, I've added this to our roadmap, thanks for your feedback!

All 7 comments

You can not have a file /foo.md and a folder foo. The file will be ignored. Docs schould be changed / explain this behavior.

Thanks @mathe42.

So what about the alternative structure I proposed, with no foo.md but with a foo/index.md? I mean - it works as long as I don't change the content:file:beforeInsert hook and foo/index.md becomes available on /foo/index. But obviously I would need to strip off the /index part to produce the desired slugs, like this:

  hooks: {
    'content:file:beforeInsert': (document) => {
      if (document.path.endsWith('/index')) {
        document.slug = document.path.replace(/^\//, '').replace(/\/index$/, '')
      } else {
        document.slug = document.path.replace(/^\//, '')
      }
    }
  }

...and that's when things break again.

It seems like the rule is that a slug must never be identical to a directory name, because the mechanism which returns the directory content takes precedence. Is that correct? This sounds like a design flaw to me: one function tries to do too much (fetching both items and listings) and ends up getting into a conflict with itself.

Hi, I'm facing the same issue. Are there any guidelines or best practices for nested subfolders? Thanks!

Hi @marcvangend

Could you try with:

const [ doc ] = await $content('', { deep: true }).where({ path: route.path.replace(/\/$/, '') }).fetch()

With this you can also remove your hook.

Hi @benjamincanac, thanks for your suggestion. Although it didn't immediately work for me, you did put me on the right track. More specifically, I didn't realize that the first argument to $content() can be an empty string, allowing you to simply fetch everything and filter from there. (I guess this could be made clearer in the docs.)

My solution

I ended up with the following code in _.vue (and indeed no more beforeInsert hook). To be clear, this works with the first file structure I suggested in the original post.

  async asyncData ({ $content, params }) {
    const path = params.pathMatch || 'index'
    const [doc] = await $content('', { deep: true }).where({ path: `/${path}` }).fetch()
    return { doc }
  }

Final thoughts

So can we close this issue? I'm tempted to say yes, because the question in the title has been answered. On the other hand, the solution does feel like a workaround.

The @nuxt/content design implicitly assumes that a file basename (without extension) and a directory name will never the same, which is obviously not true. With this assumption as starting point, and no way for the developer to override it, it builds a query reading from either a single file or an entire directory. IMHO that is the core problem that needs to be fixed.

@marcvangend The first argument of $content can actually be nothing because it defaults to /: https://content.nuxtjs.org/fetching#contentpath-options, so you can do:

await $content({ deep: true }).where({ path: `/${path}` }).fetch()

I agree with you, you should be able to query a file with the same name as a directory, I've added this to our roadmap, thanks for your feedback!

Good tip @benjamincanac, and thanks for adding it to the roadmap. Glad I could be of some assistance.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

haykokalipsis picture haykokalipsis  路  4Comments

dolbex picture dolbex  路  3Comments

Haeresis picture Haeresis  路  3Comments

davestewart picture davestewart  路  3Comments

jonathanmach picture jonathanmach  路  4Comments