nuxt / content

The file-based CMS for your Nuxt application, powered by Markdown and Vue components.
https://content.nuxt.com
MIT License
3.12k stars 623 forks source link

How to make different blog posts across different subfolders possible #882

Open wouter-muller opened 3 years ago

wouter-muller commented 3 years ago

Hello everyone,

I am struggling with the following:

I am rebuilding a Wordpress blog site to a Nuxt site with the content module. What i can not figure out is how to have the URL's match the folder structure of my content folder. So let's say my content folder is like this:

- content/
- - articles/
- - - subfolder-one/
- - - - blog-post-one.md
- - - subfolder-two/
- - - - blog-post-two.md

These blog posts should be mapped like this:

content/articles/subfolder-one/blog-post-one.md -> localhost:3000/articles/subfolder-one/blog-post-one content/articles/subfolder-two/blog-post-two.md -> localhost:3000/articles/subfolder-two/blog-post-two

So far i can only make blog posts work if they are in the content folder, not in a subfolder. If i try to access a blog post inside a subfolder i get this error:

image

In my nuxt.config.js i only added the content to the list of modules, like this:

modules: [
        "@nuxt/content",
    ],

My pages/articles/_slug.vue looks like this:

<template>
    <article>
        <nuxt-content :document="article" />
        <pre> {{ article }} </pre>
    </article>
</template>

<script>
export default {
    async asyncData({ $content, params }) {
        const article = await $content("articles", params.slug).fetch()

        return { article }
    },
}
</script>

I would really appreciate if someone could help me out here, I am quite new to both Nuxt and the content module, but have been using Vue for a few years.

wouter-muller commented 3 years ago

What would also solve my problem is to be able to add some kind add permalink or slugurl to the frontmatter of each blog post, so that decides what the URL becomes. But i tried adding this, and it didn't do anything...

NozomuIkuta commented 3 years ago

@wouter-muller

I guess you are confused by the way Nuxt Content Theme Doc generates routes.

Indeed, Nuxt Content Theme Doc generates routes (i.e. URLs) in the way you described:

content/articles/subfolder-one/blog-post-one.md -> localhost:3000/articles/subfolder-one/blog-post-one content/articles/subfolder-two/blog-post-two.md -> localhost:3000/articles/subfolder-two/blog-post-two

However, if you Nuxt Content module directly, it's up to you how directory structure under content corresponds to routes.


If you want to show the content of blog-post-one.md with the URL /articles/_slug, assuming that _slug is set subfolder-one, then you would be able to achieve that in 2 ways:

I guess these are not what you wanted.


To represent URL structure you described, you have to define 2 URL params. That is, /articles/subfolder-one/blog-post-one is a case of /articles/:category/:slug, and pages directory is:

- pages/
- - _category/
- - - _slug.vue

In this way, you can fetch the exact article at _slug.vue.

export default {
    async asyncData({ $content, params }) {
        const { category, slug } = params
        const article = await $content("articles", category,  slug).fetch()

        return { article }
    },
}

By the way, you might not know the depth of nested directories in advance. In that case, you can let Nuxt to render all articles with _.vue file at any level in pages directory.

Actually, this is how Nuxt Content Theme Docs realizes flexibility:

https://github.com/nuxt/content/blob/939caf36c547a6b4af6e303c52ed5989a5dea2f0/packages/theme-docs/src/pages/_.vue#L51-L56


To recap, you might want to read these official resources.

https://content.nuxtjs.org/fetching#contentpath-options https://nuxtjs.org/docs/2.x/features/file-system-routing/ https://nuxtjs.org/docs/2.x/features/file-system-routing/#unknown-dynamic-nested-routes

NozomuIkuta commented 3 years ago

Indeed, Nuxt Content Theme Doc generates routes (i.e. URLs) in the way you described:

content/articles/subfolder-one/blog-post-one.md -> localhost:3000/articles/subfolder-one/blog-post-one content/articles/subfolder-two/blog-post-two.md -> localhost:3000/articles/subfolder-two/blog-post-two

However, if you Nuxt Content module directly, it's up to you how directory structure under content corresponds to routes.

Let me correct.

This description was wrong, because I explained that this magic is done by _.vue file in the latter part. So Nuxt Content Theme Doc does not generates the routes actually. It just accepts all the possibilities.

ManasMadrecha commented 3 years ago

@wouter-muller As you want your URLs to correspond to your Content folder structure, best option will be to create a _.vue page. You can follow these easy steps 😋

Step 1. In the _.vue page, fetch the current post

data() {
  return {
    pathMatch: '',
    post: null,
  }
},
async fetch() {
this.pathMatch = this.$route.params.pathMatch.endsWith("/")
      ? this.$route.params.pathMatch
      : `${this.$route.params.pathMatch}/`;

    this.posts = await this.$content({ deep: true })
      .where({
        path: {
          $regex: [
            this.pathMatch.slice(0, this.pathMatch.length - 1) + "$",
            "gi"
          ]
        }
      })
      .fetch();

    this.post = this.posts[0];
}

Here, initially I have converted pathMatch because of the trailing slash. However, it is up to you how to want to handle it.

Also, Note:

  1. I have used the {deep: true} option in the above code. If you want to fetch anything from deep inside your Content folder, this should be set.
  2. You may choose some other param inside the this.$route object to match with your current post. I found pathMatch to be apt; some may find some other key to be apt like fullPath (but that also includes the hash inside the URL, so I chose pathMatch). 😁

Step 2: Display the current post

This is very easy. Just write anywhere in your HTML, <nuxt-content :document="post"></nuxt-content>. That's it 😄

Step 3: Show a list of all your posts

Why?

See here https://github.com/nuxt/content/issues/819#issuecomment-823220748

Nuxt will generate your posts' pages automatically (magically), but it will only do so if you have the link of the current post written somewhere in your entire app. And the best way to do this is to show a list of your posts, i.e. an index of blog posts. Now, how you want to show this list is up to you.

  1. You can choose a list of all the posts' links on one single page. This works if you have a simple blog.

    1. For that, you will have create a blog page inside the pages folder.
    2. Inside it, just fetch all your posts using {deep:true} option.
    3. And use v-for to display them in your HTML. Link each post using nuxt-link to post.slug.
  2. OR You can show only the children posts' links inside a parent post. This is better if you have a deeply nested Content structure with thousands of md files.

    1. For that, see here https://github.com/nuxt/content/issues/860#issuecomment-823172760
    2. Use {deep: true} and fetch only the children posts based on where: {dir: this.post.path}
    3. And use v-for to display them in your HTML. Link each post using nuxt-link to post.slug.

And that's it. Enjoy Nuxt Content ♥

Regarding custom slugurl for SEO

You may use the md file name as the slug. If you want to keep your file name anything and your URL something else, you may add in your each md file blog post a new key slugurl or something like that.

Now, when you fetch all your posts, you may display them using v-for with nuxt-link pointing to post.slugurl inside of post.slug.

Note that, now when you click on any of the post using the above links, you will go the current post, but it won't show any contents of that current post. This is because now, current post's $route.params.pathMatch will not be equal to the current post's path used in where. (This is logical because path comes from the Content folder's sub-folders structure, whereas $route.params.pathMatch comes from the URL.) So, basically your where query won't work with the above code now.

Quick Solution? So, better to keep your filenames equal to the URL to want for SEO.

But, what if you still want to keep them different? So, how to fetch the contents of the current post then?

Here, you will have to try some try some other permutation or combination which makes you fetch the current post more easily 😊.

You can use something like, where: {slugurl: this.$route.params.slugurl}. This will work perfectly, provided all your md files have a unique slugurl, which is logical, isn't it? 😋

Non-unique slugurl

If you have limited and diverse blogposts, your slugurls will definitely will be different.

But let's say you have thousands of blogposts arranged month-wise into hundreds of folders, and you can practically only scroll and check a single folder to verify the blog's slugurl's uniqueness. Then, in such cases it is better to finetune the where query with dir key, so that only blogposts inside a certain folder are fetched, i.e. only fetch sibling posts from a folder, and not all the thousand posts.

  1. Create new variables inside the data() e.g. urlWithoutSlugurl and postSlugurl.
  2. Use Use JS String methods to assigned them the URL without the ending portions of slugurl. E.g.
    • if URL is website.com/articles/subfolderone/abcGoodSEOlink, then
      • this.urlWithoutSlugurl will be website.com/articles/subfolderone and
      • postSlugurl will be abcGoodSEOlink.
  3. Now update the fetch logic to this
    where({
        $and: [{ dir: this.urlWithoutSlugurl }, { slugurl: this.postSlugurl }]
      })

Side note: If you don't want to use a _.vue page, you can create some nested dynamic pages like _slugurl.vue (but how many levels nested? and what if you later change your Content structure? so better to create _.vue page 😁). However, if you still choose to have a simple dynamic page (not _.vue), then your fetch logic will need to be altered. You can see here https://github.com/nuxt/content/issues/822#issuecomment-799894393

Hope all this helps. 💛