NextJs: exportação estática com rotas dinâmicas
Estou um pouco confuso com a documentação e não tenho certeza se é possível o que estou tentando fazer.
Meta:
- Exporte o aplicativo NextJS estaticamente e hospede-o no netlify
- Permitir que os usuários criem postagens e tenham links para essas postagens que funcionem
Por exemplo:
- O usuário cria uma nova postagem com o id: 2
- Esta postagem deve ser acessível publicamente em
mysite.com/posts/2
Eu imagino que posso criar um arquivo html esqueleto chamado posts.htmle o netlify redireciona todas as posts/<id>solicitações para esse posts.htmlarquivo, que mostrará o esqueleto e carregará os dados necessários por meio de uma API em tempo real.
Eu acho que sem esse hack netlify, meu objetivo de exportação estática + links de trabalho para rotas dinâmicas não é possível com next.js de acordo com sua documentação, pois fallback: truesó é possível usando SSR.
Pergunta: Como posso alcançar a configuração dos meus sonhos de exportação estática do nextjs + links de trabalho para rotas dinâmicas?
EDIT: Acabei de descobrir sobre Redirecionamentos . Eles podem ser a solução para o meu problema.
Respostas
getStaticProps e getStaticPaths()
Parece que usar getStaticProps e getStaticPaths() é o caminho a seguir.
Eu tenho algo assim no meu [post].jsarquivo:
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,
};
}
No meu caso, eu consulto Contentful usando my getAllBlogPostEntriespara as entradas do blog. Isso cria os arquivos, algo como this-is-my-post.html. getBlogPostContent(post)irá pegar o conteúdo para o arquivo específico.
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;
}
Quando eu faço um npm run exportele cria um arquivo para cada postagem do blog ...
info - Collecting page data ...[
{
params: { post: 'my-first-post' }
},
{
params: { post: 'another-post' }
},
No seu caso route, seria apenas 1, 2, 3, etc.
Método desatualizado - Execute uma consulta em next.config.js
Se você deseja criar um site estático, precisará consultar as postagens com antecedência, antes do arquivo next export.
Aqui está um exemplo usando Contentful que você pode ter configurado com postagens de blog:
Primeiro crie uma página em pages/blog/[post].js.
Next pode usar um exportMapinside 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;
},
};
Logo acima return routes;, estou me conectando ao Contentful e pegando todas as postagens do blog. Neste caso cada post tem um valor que defini chamado route. Eu dei a cada conteúdo um valor de rota, algo como this-is-my-first-poste just-started-blogging. No final, o objeto de rota se parece com isso:
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]' },
};
Sua exportação no out/diretório será:
out/
/index.html
/blog/index.html
/blog/this-is-my-first-post.html
/blog/just-started-blogging.html
No seu caso, se você estiver usando números de ID de postagem, você teria que buscar as postagens do blog e fazer algo como:
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]' },
// };
O próximo passo seria criar algum tipo de gancho no Netlify para acionar uma construção de site estático quando o usuário criasse conteúdo.
Também aqui está uma ideia de como pages/blog/[post].jsseria o seu.
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;