I am a bit confused by the documentation and not sure if it's possible what I am trying to do.
Goal:
For example:
mysite.com/posts/2
I'd imagine that I can create a skeleton html file called posts.html
and netlify redirects all posts/<id>
requests to that posts.html
file which will then show the skeleton and load the necessary data via an API on the fly.
I think without this netlify hack, my goal of static export + working links to dynamic routes is not possible with next.js according to their documentation since fallback: true
is only possible when using SSR.
Question: How can I achieve my dream setup of static nextjs export + working links to dynamic routes?
EDIT: I just found out about Redirects. They could be the solution to my problem.
It looks like using getStaticProps and getStaticPaths() is the way to go.
I have something like this in my [post].js
file:
const Post = ({ pageContent }) => {
// ...
}
export default Post;
export async function getStaticProps({ params: { post } }) {
const [pageContent] = await Promise.all([getBlogPostContent(post)]);
return { props: { pageContent } };
}
export async function getStaticPaths() {
const [posts] = await Promise.all([getAllBlogPostEntries()]);
const paths = posts.entries.map((c) => {
return { params: { post: c.route } }; // Route is something like "this-is-my-post"
});
return {
paths,
fallback: false,
};
}
In my case, I query Contentful using my getAllBlogPostEntries
for the blog entries. That creates the files, something like this-is-my-post.html
. getBlogPostContent(post)
will grab the content for the specific file.
export async function getAllBlogPostEntries() {
const posts = await client.getEntries({
content_type: 'blogPost',
order: 'fields.date',
});
return posts;
}
export async function getBlogPostContent(route) {
const post = await client.getEntries({
content_type: 'blogPost',
'fields.route': route,
});
return post;
}
When I do an npm run export
it creates a file for each blog post...
info - Collecting page data ...[
{
params: { post: 'my-first-post' }
},
{
params: { post: 'another-post' }
},
In your case the route
would just be 1, 2, 3, etc.
If you are looking to create a static site you would need to query the posts ahead of time, before the next export
.
Here is an example using Contentful which you might have set up with blog posts:
First create a page under pages/blog/[post].js
.
Next can use an exportMap
inside next.config.js
.
// next.config.js
const contentful = require('contentful');
// Connects to Contentful
const contentfulClient = async () => {
const client = await contentful.createClient({
space: process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID,
accessToken: process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN,
});
return client;
};
// Gets all of the blog posts
const getBlogPostEntries = async (client) => {
const entries = await client.getEntries({
content_type: 'blogPost',
order: 'fields.date',
});
return entries;
};
module.exports = {
async exportPathMap() {
const routes = {
'/': { page: '/' }, // Index page
'/blog/index': { page: '/blog' }, // Blog page
};
const client = await contentfulClient();
const posts = await getBlogPostEntries(client);
// See explanation below
posts.items.forEach((item) => {
routes[`/blog/${item.fields.route}`] = { page: '/blog/[post]' };
});
return routes;
},
};
Just above return routes;
I'm connecting to Contentful, and grabbing all of the blog posts. In this case each post has a value I've defined called route. I've given every piece of content a route value, something like this-is-my-first-post
and just-started-blogging
. In the end, the route object looks something like this:
routes = {
'/': { page: '/' }, // Index page
'/blog/index': { page: '/blog' }, // Blog page
'/blog/this-is-my-first-post': { page: '/blog/[post]' },
'/blog/just-started-blogging': { page: '/blog/[post]' },
};
Your export in the out/
directory will be:
out/
/index.html
/blog/index.html
/blog/this-is-my-first-post.html
/blog/just-started-blogging.html
In your case, if you are using post id numbers, you would have to fetch the blog posts and do something like:
const posts = await getAllPosts();
posts.forEach((post) => {
routes[`/blog/${post.id}`] = { page: '/blog/[post]' };
});
// Routes end up like
// routes = {
// '/': { page: '/' }, // Index page
// '/blog/index': { page: '/blog' }, // Blog page
// '/blog/1': { page: '/blog/[post]' },
// '/blog/2': { page: '/blog/[post]' },
// };
The next step would be to create some sort of hook on Netlify to trigger a static site build when the user creates content.
Also here is and idea of what your pages/blog/[post].js
would look like.
import Head from 'next/head';
export async function getBlogPostContent(route) {
const post = await client.getEntries({
content_type: 'blogPost',
'fields.route': route,
});
return post;
}
const Post = (props) => {
const { title, content } = props;
return (
<>
<Head>
<title>{title}</title>
</Head>
{content}
</>
);
};
Post.getInitialProps = async ({ asPath }) => {
// asPath is something like `/blog/this-is-my-first-post`
const pageContent = await getBlogPostContent(asPath.replace('/blog/', ''));
const { items } = pageContent;
const { title, content } = items[0].fields;
return { title, content };
};
export default Post;