Markdown file not found when using "getDocumentBySlug" only on production
mschinis opened this issue · 5 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.


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