next.js – nested markdown pages in typescript nextjs website

I’ve a problem to get a nested page in my nextjs website.

Here my folder structure :

- _post
 - services
  - folder 1
   - .MD file
  - .MD file2

Here my page structure :

- page
 - services
 - [slug].tsx
 - [slug]
  - [page].tsx

api.tsx

import fs from "fs";
import { join } from "path";
import matter from "gray-matter";

export function getPostSlugs(folder: string) {
  const postsDirectory = join(process.cwd(), folder);
  return fs.readdirSync(postsDirectory);
}

export function getPostBySlug(
  slug: string,
  fields: string[] = [],
  postsDirectory: string
) {
  const realSlug = slug.replace(/.md$/, "");
  const fullPath = join(postsDirectory, `${realSlug}.md`);
  const fileContents = fs.readFileSync(fullPath, "utf8");
  const { data, content } = matter(fileContents);

  type Items = {
    [key: string]: string;
  };

  const items: Items = {};

  // Ensure only the minimal needed data is exposed
  fields.forEach((field) => {
    if (field === "slug") {
      items[field] = realSlug;
    }
    if (field === "content") {
      items[field] = content;
    }

    if (typeof data[field] !== "undefined") {
      items[field] = data[field];
    }
  });

  return items;
}

export function getAllPosts(fields: string[] = [], folder: string) {
  const slugs = fs.readdirSync(join(process.cwd(), folder)).filter((slug) => {
    return slug.endsWith(".md");
  });
  const posts = slugs.map((slug: string) =>
    getPostBySlug(slug, fields, folder)
  );
  return posts;
}

[slug].tsx

import { useRouter } from "next/router";
import ErrorPage from "next/error";
import Container from "../../components/Container";
import PostBody from "../../components/PostBody";
import Layout from "../../components/Layout";
import PostTitle from "../../components/PostTitle";
import PostType from "../../types/post";
import { useEffect } from "react";
import AOS from "aos";
import Contact from "../../components/Contact";
import MoreStories from "../../components/MoreStories";
import TwoColumnsLayout from "../../components/TwoColumnsLayout";
import ActiveLink from "../../components/Link";
import { getAllPosts, getPostBySlug } from "../../lib/api";
import markdownToHtml from "../../lib/markdownToHtml";
import OtherStories from "../../components/OtherStories";

type Props = {
  post: PostType;
  allPosts: PostType[];
  postsSuite: PostType[];
  preview?: boolean;
};

const Post = ({ allPosts, postsSuite, post, preview }: Props) => {
  const otherPosts = allPosts?.filter((p) => p.slug !== post.slug);
  const morePosts = postsSuite;
  useEffect(() => {
    AOS.init({ once: false, offset: -70, delay: 100, duration: 400 });
  }, []);
  const router = useRouter();
  if (!router.isFallback && !post?.slug) {
    return <ErrorPage statusCode={404} />;
  }
  return (
    <Layout preview={preview}>
      {router.isFallback ? (
        <PostTitle>Loading…</PostTitle>
      ) : (
        <>
          <Container>
            <section className="items-center md:justify-between mb-20">
              <div className="hero-container">
                <div className="environment"></div>
                <h4 className="text-center text-lg">{post.subtitle}</h4>
                <h1 className="hero glitch layers" data-text="gifted">
                  <span>
                    <h1>{post.title}</h1>
                  </span>
                </h1>
                <div className="wrapper container">
                  <p className="small">{post.summary}</p>
                </div>
              </div>
              <div className="wrapper container">
                <ActiveLink
                  href={"../contact"}
                  className="m-auto bg-black hover:bg-white hover:text-black border border-white text-white font-bold py-3 px-12 lg:px-8 duration-200 transition-colors lg:mb-0"
                  activeClassName={""}
                >
                  Nous contacter
                </ActiveLink>
              </div>
            </section>
            <TwoColumnsLayout
              title="Introduction"
              paragraph={post.introduction}
            />
            <div className="wrapper container">
              <PostBody content={post.content} />
            </div>
            {morePosts?.length > 0 && (
              <MoreStories
                posts={morePosts}
                h1Title={`${post.title} : En savoir plus`}
              />
            )}
            {otherPosts?.length > 0 && (
              <OtherStories
                posts={otherPosts}
                h1Title="Découvrir nos autres services en développement web, pentesting et
              audit web"
              />
            )}
          </Container>

          <Contact />
        </>
      )}
      <style jsx>{`
        .wrapper {
          max-width: 900px;
          align-items: center;
          display: flex;
          flex-direction: column;
          margin: auto;
        }
        h1 {
          margin-top: 0.2em;
          margin-bottom: 0;
          align-self: center;
          text-align: center;
        }
        h2.small {
          margin-top: 2em;
        }

        p {
          margin: 0;
          margin-bottom: 7em;
          margin-top: 7em;
        }

        p.small {
          margin: 0;
          margin-bottom: 5em;
          margin-top: 2em;
        }

        h4 {
          margin-bottom: 0;
        }

        h4.small {
          margin-bottom: 8em;
        }

        figure {
          margin: 0;
        }

        .elgif {
          min-height: 300px;
        }

        .elgif img {
          width: 50%;
          margin: 0 auto;
          z-index: 3;
        }

        .elgif:before,
        .elgif:after {
          position: absolute;
          content: "";
          width: 100%;
          display: block;
        }

        .elgif:before {
          z-index: 2;
          top: 11px;
          right: 11px;
        }

        .elgif:after {
          z-index: 1;
          top: 25px;
          right: 23px;
        }

        @media (min-width: 300px) {
          .elgif {
            min-height: 300px;
          }
        }
      `}</style>
    </Layout>
  );
};

export default Post;

type Params = {
  params: {
    slug: string;
  };
};

export async function getStaticProps({ params }: Params) {
  const post = getPostBySlug(
    params.slug,
    [
      "title",
      "slug",
      "content",
      "ogImage",
      "coverImage",
      "introduction",
      "subtitle",
      "summary",
    ],
    "_posts/services"
  );

  const postsSuite = getAllPosts(
    ["title", "slug", "coverImage", "excerpt"],
    `_posts/services/${post.slug}`
  );

  const allPosts = getAllPosts(
    ["title", "slug", "coverImage", "excerpt"],
    `_posts/services`
  );

  const content = await markdownToHtml(post.content || "");

  return {
    props: {
      post: {
        ...post,
        content,
      },
      postsSuite,
      allPosts,
    },
  };
}

export async function getStaticPaths() {
  const posts = getAllPosts(["slug"], "_posts/services");

  return {
    paths: posts.map((post) => {
      return {
        params: {
          slug: post.slug,
        },
      };
    }),
    fallback: false,
  };
}

But I don’t know how to get props and paths for my [page].tsx file. I’ve tried to get multiple params to have an url like../[slug]/[page] but it doesn’t work

Could you help me please?

Leave a Comment