avitorio / outstatic

Outstatic - A static CMS for Next.js

Home Page:https://Outstatic.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Markdown file not found when using "getDocumentBySlug" only on production

mschinis opened this issue · comments

Provide environment information

outstatic: 1.4.7
react: 18
next: 14.1.4

What browser are you using? (if relevant)

Arc 1.36.0

How are you deploying your application? (if relevant)

Vercel

Describe the Bug

I followed the Outstatic setup guide, to setup outstatic on our existing next.js project.
While running locally, routing to /blog and /blog/[slug] work correctly.

When deployed with Vercel, the /blog page works as expected, but once I visit a blog post on /blog/[slug], I get a 404 page.

Looking at the logs, it looks like the markdown path is correct, but I get an error that the markdown file could not be found. Attaching images of our folder structure, and the Vercel logs.

Screenshot 2024-03-30 at 12 32 08 AM Screenshot 2024-03-30 at 12 26 57 AM

Expected Behavior

The markdown file should be loaded correctly.

Link to reproduction - Issues with a link to complete (but minimal) reproduction code will be addressed faster

https://gist.github.com/mschinis/8ec4c9256493c5d7d988ca384be2dce9

To Reproduce

I believe copying the files from gist, and deploying it to vercel should make it replicable.

Closing as it turns out this was an issue on my end.

Missed this section of the guide.

Adding the following to /src/app/blog/[slug]/page.tsx fixed the issue.

export async function generateStaticParams() {
    const posts = getDocumentSlugs('posts')
    return posts.map(slug => ({ slug }))
}

Hey @mschinis - thanks for the follow up and I'm glad you figured it out! Let me know if you face any other issues.

I'm having similar issue, below is the error when deployed to Vercel:

[Error: ENOENT: no such file or directory, open '/var/task/outstatic/content/metadata.json'] {
errno: -2,
code: 'ENOENT',
syscall: 'open',
path: '/var/task/outstatic/content/metadata.json',
digest: '2192603207'
}

My /src/app/blog/[slug]/page.tsx

import CustomReactMarkdown, {
  getTableOfContents,
} from "@/components/CustomReactMarkdown";
import { getDocumentBySlug, getDocumentSlugs, load } from "outstatic/server";
import Image from "next/image";
import { format, parseISO } from "date-fns";
import { TracingBeam } from "@/components/ui/tracing-beam";
import { Metadata } from "next";
import { absoluteUrl } from "@/lib/utils";
import { notFound } from "next/navigation";

interface Params {
  params: {
    slug: string;
  };
}

export const revalidate = 60;

export async function generateStaticParams() {
  const posts = getDocumentSlugs("posts");
  return posts.map((slug) => ({ slug }));
}

// export async function generateMetadata({
//   params: { slug },
// }: any): Promise<Metadata> {
//   const post = await getData(slug);

//   if (!post) {
//     return {};
//   }

//   return {
//     title: post.title,
//     description: post.description,
//     openGraph: {
//       title: post.title,
//       description: post.description,
//       type: "article",
//       url: absoluteUrl(`/blog/${post.slug}`),
//       images: [
//         {
//           url: absoluteUrl(
//             post?.coverImage || "/images/post_placeholder_image.avif"
//           ),
//           width: 1200,
//           height: 630,
//           alt: post.title,
//         },
//       ],
//     },
//     twitter: {
//       card: "summary_large_image",
//       title: post.title,
//       description: post.description,
//       images: absoluteUrl(
//         post?.coverImage || "/images/post_placeholder_image.avif"
//       ),
//     },
//   };
// }

export default async function Post({ params: { slug } }: Params) {
  const post = await getData(slug);

  // const toc = getTableOfContents(post.content);

  if (!post) return <></>;

  return (
    <article className="container items-start">
      <TracingBeam>
        <div className="py-8 text-xl lg:text-5xl font-black">{post.title}</div>
        <Date dateString={post.publishedAt} />
        <Description description={post.description} />
        <PostCoverImage imageUrl={post.coverImage} />
        <CustomReactMarkdown markdown={post.content} />
      </TracingBeam>
    </article>
  );

  // return (
  //   <div className="flex-wrap xl:flex mx-auto place-content-center">
  //     <TableOfContents toc={toc} />

  //     <article className="grow container place-content-center items-center">
  //       <div className="py-8 text-xl lg:text-5xl font-black">{post.title}</div>

  //       <CustomReactMarkdown markdown={post.content} />
  //     </article>
  //   </div>
  // );
}

async function getData(slug: string): Promise<any> {
  const db = await load();
  const post = await db
    .find({
      collection: "posts",
      slug: slug,
    })
    .project([
      "title",
      "description",
      "publishedAt",
      "slug",
      "author",
      "content",
      "coverImage",
      "category",
      "tags",
      "featured",
    ])
    .first();

  if (!post) {
    notFound();
  }

  return {
    props: {
      post: {
        ...post,
      },
    },
  };
}

function PostCoverImage({ imageUrl }: { imageUrl?: string }) {
  if (!imageUrl || imageUrl === "") {
    return <></>;
  }

  return (
    <div className="relative h-[20rem] my-4">
      <Image
        src={imageUrl}
        alt="Cover Image"
        style={{ objectFit: "cover" }}
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, (max-width: 1280px) 33vw, 33vw"
        className="rounded-lg"
      />
    </div>
  );
}

function Date({ dateString }: { dateString: string }) {
  if (!dateString) return <></>;

  const date = parseISO(dateString);
  const formattedDate = format(date, "MMMM d, yyyy");

  return <div className="text-sm opacity-50">{formattedDate}</div>;
}

function Description({ description }: { description: string | undefined }) {
  if (!description) return <></>;
  return <div className="text-lg opacity-80">{description}</div>;
}

It seems like the page is there after initial deployment, but after revalidate it's gone.

Set revalidate to false.
And add const dynamic = 'force-static'.

Let me know if this works

Set revalidate to false. And add const dynamic = 'force-static'.

Let me know if this works

Thank you very much! It works! @avitorio