I’m working on a project where I use WordPress as a headless CMS and Next.js for the frontend. The setup fetches page content from WordPress and displays it as JSON data on the frontend.
The main problem: Some pages randomly return 404 errors, but this only happens in production. My local development environment and staging server work perfectly fine.
Background context: My WordPress server sometimes goes down, so I added a caching system that saves JSON files locally. When the CMS is unavailable, the site serves content from these cached files instead of showing server errors.
What I’ve noticed: When I navigate through the site, Next.js makes requests to URLs that include a build ID. If this ID keeps changing, could that be causing the 404 issues? The pattern looks like this: /_next/data/[BUILD_ID]/page-name.json
Here’s my server-side code that handles the data fetching:
export const getStaticProps: GetStaticProps = async ({ params }) => {
const cachePath = path.join(process.cwd(), 'cache/content');
if (!fs.existsSync(cachePath)) {
fs.mkdirSync(cachePath, { recursive: true });
}
// Get navigation and site data
const mainNav: iNavigation = await apiCall('/menus/v1/menus/main');
const socialLinks: iNavigation = await apiCall('/menus/v1/menus/social');
const siteFooter: iNavigation = await apiCall('/menus/v1/menus/site-footer');
const siteEmails: iEmails = await apiCall('/acf/v2/options/contact-info');
const allPages = await apiCall('/wp/v2/pages', { params: { per_page: 1 } });
const navigation = {
main: processNavigation(mainNav),
footer: processNavigation(siteFooter),
social: socialLinks ? socialLinks.items?.map(({ url }) => ({ url })) : {},
};
const latestUpdate = allPages?.[0]?.modified;
const emailContacts = siteEmails?.contact_info;
const navCachePath = path.join(cachePath, `navigation.json`);
const emailCachePath = path.join(cachePath, `emails.json`);
const updateCachePath = path.join(cachePath, `update-time.json`);
let navData, emailData, updateData;
if (latestUpdate && emailContacts && navigation.main && navigation.footer) {
fs.writeFileSync(navCachePath, JSON.stringify(navigation, null, 2));
fs.writeFileSync(emailCachePath, JSON.stringify(emailContacts, null, 2));
fs.writeFileSync(updateCachePath, JSON.stringify(latestUpdate, null, 2));
}
navData = fs.readFileSync(navCachePath, 'utf-8');
emailData = fs.readFileSync(emailCachePath, 'utf-8');
updateData = fs.readFileSync(updateCachePath, 'utf-8');
// Return 404 for invalid routes
if (!params?.route || !Array.isArray(params.route)) {
return {
redirect: {
destination: '/404',
permanent: false,
},
};
}
let pageContent = null;
let finalUrl = '';
let hasError = false;
// Process each URL segment
for (let index = 0; index < params.route.length; index++) {
const segment = params.route[index];
const segmentCachePath = path.join(cachePath, `${segment}.json`);
const contentData = await fetchPageContent(segment);
if (contentData) {
fs.writeFileSync(segmentCachePath, JSON.stringify(contentData, null, 2));
pageContent = {
...contentData,
meta_description: contentData.meta_description ?? null,
};
finalUrl += `/${segment}`;
} else if (fs.existsSync(segmentCachePath)) {
try {
const cachedContent = fs.readFileSync(segmentCachePath, 'utf-8');
const parsedData = JSON.parse(cachedContent);
pageContent = {
...parsedData,
meta_description: parsedData.meta_description ?? null,
};
finalUrl += `/${segment}`;
} catch (err) {
console.error(`Cache read failed for ${segment}:`, err);
}
} else {
if (index === params.route.length - 1) {
hasError = true;
} else {
continue;
}
}
}
// Show 404 if no content found
if (hasError || !pageContent) {
return {
props: {
pageData: {
status: 404,
page_title: 'Content Not Found',
error_message: 'Sorry, the page you are looking for does not exist.',
},
},
};
}
// Redirect if URL structure is wrong
const requestedPath = '/' + params.route.join('/');
if (finalUrl && requestedPath !== finalUrl) {
return {
redirect: {
destination: finalUrl,
permanent: false,
},
};
}
return {
props: {
pageData: pageContent,
navigation: JSON.parse(navData),
emails: JSON.parse(emailData),
lastModified: JSON.parse(updateData),
},
};
};
Any ideas why this works locally but fails in production? Could the changing build ID be the root cause?