NextJs:动态路由静态导出
NextJs: Static export with dynamic routes
我对文档有点困惑,不确定我正在尝试做的事情是否可行。
目标:
- 静态导出 NextJS 应用程序并将其托管在 netlify 上
- 允许用户创建 posts 并链接到这些有效的 posts
例如:
- 用户创建了一个新的 post,id 为:2
- 这个 post 应该可以在
mysite.com/posts/2
下公开访问
我想象我可以创建一个名为 posts.html
的框架 html 文件,netlify 将所有 posts/<id>
请求重定向到那个 posts.html
文件,然后显示框架并通过 API 动态加载必要的数据。
我认为如果没有这个 netlify hack,根据他们的 documentation,我的静态导出 + 到动态路由的工作链接的目标是不可能的,因为 fallback: true
只有在使用 SSR。
问题:如何实现静态 nextjs 导出 + 动态路由的工作链接的梦想设置?
编辑:
我刚知道 Redirects。它们可能是我问题的解决方案。
getStaticProps 和 getStaticPaths()
看起来使用 getStaticProps 和 getStaticPaths() 是可行的方法。
我的 [post].js
文件中有这样的内容:
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,
};
}
就我而言,我使用 getAllBlogPostEntries
查询 Contentful 以获取博客条目。这会创建文件,类似于 this-is-my-post.html
。 getBlogPostContent(post)
将抓取特定文件的内容。
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;
}
当我执行 npm run export
时,它会为每个博客创建一个文件 post...
info - Collecting page data ...[
{
params: { post: 'my-first-post' }
},
{
params: { post: 'another-post' }
},
在您的情况下,route
将只是 1、2、3 等
过时的方法 - 运行 next.config.js
中的查询
如果您要创建静态站点,则需要在 next export
.
之前提前查询 post
这是一个使用 Contentful 的示例,您可能已经在博客 posts 中设置了它:
首先在pages/blog/[post].js
下创建一个页面。
接下来可以在next.config.js
里面用一个exportMap
。
// 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;
},
};
就在 return routes;
上面,我正在连接到 Contentful,并获取所有博客 post。在这种情况下,每个 post 都有一个我定义的名为 route 的值。我为每条内容都指定了一个路由值,例如 this-is-my-first-post
和 just-started-blogging
。最后,路由对象看起来像这样:
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]' },
};
您在 out/
目录中的导出将是:
out/
/index.html
/blog/index.html
/blog/this-is-my-first-post.html
/blog/just-started-blogging.html
在您的情况下,如果您使用 post id 号码,则必须获取博客 posts 并执行以下操作:
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]' },
// };
下一步是在 Netlify 上创建某种挂钩,以在用户创建内容时触发静态站点构建。
这里还有关于您的 pages/blog/[post].js
的想法。
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;
我对文档有点困惑,不确定我正在尝试做的事情是否可行。
目标:
- 静态导出 NextJS 应用程序并将其托管在 netlify 上
- 允许用户创建 posts 并链接到这些有效的 posts
例如:
- 用户创建了一个新的 post,id 为:2
- 这个 post 应该可以在
mysite.com/posts/2
下公开访问
我想象我可以创建一个名为 posts.html
的框架 html 文件,netlify 将所有 posts/<id>
请求重定向到那个 posts.html
文件,然后显示框架并通过 API 动态加载必要的数据。
我认为如果没有这个 netlify hack,根据他们的 documentation,我的静态导出 + 到动态路由的工作链接的目标是不可能的,因为 fallback: true
只有在使用 SSR。
问题:如何实现静态 nextjs 导出 + 动态路由的工作链接的梦想设置?
编辑: 我刚知道 Redirects。它们可能是我问题的解决方案。
getStaticProps 和 getStaticPaths()
看起来使用 getStaticProps 和 getStaticPaths() 是可行的方法。
我的 [post].js
文件中有这样的内容:
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,
};
}
就我而言,我使用 getAllBlogPostEntries
查询 Contentful 以获取博客条目。这会创建文件,类似于 this-is-my-post.html
。 getBlogPostContent(post)
将抓取特定文件的内容。
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;
}
当我执行 npm run export
时,它会为每个博客创建一个文件 post...
info - Collecting page data ...[
{
params: { post: 'my-first-post' }
},
{
params: { post: 'another-post' }
},
在您的情况下,route
将只是 1、2、3 等
过时的方法 - 运行 next.config.js
中的查询如果您要创建静态站点,则需要在 next export
.
这是一个使用 Contentful 的示例,您可能已经在博客 posts 中设置了它:
首先在pages/blog/[post].js
下创建一个页面。
接下来可以在next.config.js
里面用一个exportMap
。
// 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;
},
};
就在 return routes;
上面,我正在连接到 Contentful,并获取所有博客 post。在这种情况下,每个 post 都有一个我定义的名为 route 的值。我为每条内容都指定了一个路由值,例如 this-is-my-first-post
和 just-started-blogging
。最后,路由对象看起来像这样:
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]' },
};
您在 out/
目录中的导出将是:
out/
/index.html
/blog/index.html
/blog/this-is-my-first-post.html
/blog/just-started-blogging.html
在您的情况下,如果您使用 post id 号码,则必须获取博客 posts 并执行以下操作:
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]' },
// };
下一步是在 Netlify 上创建某种挂钩,以在用户创建内容时触发静态站点构建。
这里还有关于您的 pages/blog/[post].js
的想法。
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;