Fixing 404 Errors in Dynamic Routes with Proper Slug Generation
Fixing 404 Errors in Dynamic Routes with Proper Slug Generation
The Challenge: 404 Errors on Valid Dynamic Routes
When implementing a dynamic route for multiple content collections in Astro (
/vibe-with/[collection]/[...slug].astro), we encountered 404 errors when trying to access valid content. The server logs showed: text
[WARN] [router] A `getStaticPaths()` route pattern was matched, but no matching static path was found for requested path `/vibe-with/prompts/write-a-comprehensive-squash-merge`. This was happening despite:
- The route pattern being correctly defined
- The content existing in the collection
- The URL being correctly constructed in the PostCard component
Incorrect Attempts
Attempt 1: Using the full path for slug generation
In our first implementation, we were generating slugs using the full file path:
typescript
// Map each prompts entry to a static path object
const promptsPaths = promptsEntries.map(entry => {
const filename = entry.id.replace(/\.md$/, '');
// INCORRECT: Using the full path to generate slugs
const generatedSlug = filename.toLowerCase().replace(/\s+/g, '-');
if (!entry.data.slug) entry.data.slug = generatedSlug;
if (!entry.data.title) entry.data.title = toProperCase(baseFilename);
const slug = entry.data.slug;
return {
params: { collection: 'prompts', slug },
props: {
entry,
collection: 'prompts',
},
};
}); This caused issues because in Astro content collections,
entry.id often contains the full path (e.g., workflow/write-a-comprehensive-squash-merge.md). When we used this to generate slugs, we were creating slugs like workflow-write-a-comprehensive-squash-merge instead of just write-a-comprehensive-squash-merge.Attempt 2: Fixing TypeScript errors but not the slug generation
We tried to fix TypeScript errors by creating safe data objects:
typescript
const safeData: EntryData = {
...data,
tags: Array.isArray(data.tags) ? data.tags : [],
slug: data.slug || generatedSlug,
title: data.title || toProperCase(baseFilename)
}; But we were still using the incorrect slug generation method.
The "Aha!" Moment
The issue was a mismatch between how we were generating slugs in
getStaticPaths() and how the URLs were being constructed in the PostCard components.Looking at the
[magazine].astro file, we found that URLs were being generated using just the basename: typescript
// In [magazine].astro
return {
...entry.data,
id: entry.id,
url: `${urlPrefix}${slug}` // Unified route: /vibe-with/[collection]/[slug]
}; Where
slug was derived from just the filename, not the full path: typescript
const pathParts = entry.id.split('/');
const filename = pathParts[pathParts.length - 1].replace(/\.md$/, '');
const slug = filename.toLowerCase().replace(/\s+/g, '-'); The Solution
We needed to modify our slug generation in
getStaticPaths() to use only the basename (not the full path): typescript
// Extract just the filename without path and extension
const filename = entry.id.replace(/\.md$/, '');
const filenameParts = filename.split('/');
const baseFilename = filenameParts[filenameParts.length - 1];
// Generate slug from the basename only (not the full path)
// This matches how URLs are constructed in PostCard components
const generatedSlug = baseFilename.toLowerCase().replace(/\s+/g, '-'); This ensures that the slugs generated in
getStaticPaths() match the slugs used in the URL construction in the PostCard components.Additional Improvements
- We added debug logging to see the generated slugs during build:
typescript
console.log(`DEBUG SLUG for ${entry.id}: Generated=${generatedSlug}, Existing=${data.slug || 'none'}`); - We ensured all Astro object usage was inside the render context by using an async IIFE:
typescript
{(async () => {
const { entry, collection } = Astro.props;
// Now we can use await here
if (!entry || !entry.data || !entry.data.title) {
const entry = await getEntry(collection, slug);
// ...
}
})()} Key Learnings
- In Astro dynamic routes, ensure that slug generation in
getStaticPaths()matches the URL construction in your components. - When working with file paths in content collections, be careful about using the full path vs. just the basename.
- Use debug logging during build to verify that slugs are being generated correctly.
- Remember that all Astro object usage (
Astro.params,Astro.url,Astro.props) must be inside the render context, and any async operations need to be in an async function. - The TypeScript type system can help catch these issues if you use proper type guards and assertions.
Best Practices for Next Time
- Always check how URLs are being constructed in your components before implementing
getStaticPaths(). - Add debug logging for critical path generation during development.
- Use a consistent approach to slug generation across your codebase.
- Consider adding a utility function for slug generation to ensure consistency.
- Test dynamic routes with various content structures to ensure robustness.