摘要/问题
我使用 Nextjs
创建了一个动漫数据库应用,并部署在 Vercel
上。构建很好并且呈现了初始页面,但只有少数动态路由被呈现,其余的显示 404 页面。我进入部署日志,发现对于每个动态页面,每个动态路由只构建了 10 条路由。
部署 Vercel 的屏幕截图
在开发过程中(localhost:3000),没有出现任何问题,一切都运行良好。
路线基于每个标题的id
,并且有数千个标题。
我的代码
这是我使用 getStaticPaths
和 getStaticProps
的其中一个页面的代码
export const getStaticProps = async ({ params }) => {
const [anime, animeCharacters, categories, streaming, reviews] = await Promise.all([
fetch(`https://kitsu.io/api/edge/anime/${params.id}`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/characters`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/categories`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/streaming-links`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/reviews`),
])
.then((responses) =>
Promise.all(responses.map((response) => response.json()))
)
.catch((e) => console.log(e, "There was an error retrieving the data"))
return { props: { anime, animeCharacters, categories, streaming, reviews } }
}
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
return { paths, fallback: false }
}
[id]
是我的动态路线,如您所见,它只填充了 10 条路线(前 3 条和另外 7 条)。
尽管节目数量很多,但我都会循环每个节目并获取其 ID,然后将其作为路径传递。
我的想法
我使用的 API 是 Kitsu API。
在文档中,它指出:“资源默认以 10 个为一组进行分页,最多可以增加到 20 个”。我想这可能就是生成 10 个路径的原因,但如果是这样的话,那么为什么它在生产和部署中都能正常工作呢?另外,当我单击每个海报图像时,它应该通过其 id 将我带到该特定标题,该标题是动态的,因此最初生成多少资源并不重要。
动态页面代码`/anime/[id]
import { useState } from "react"
import { useRouter } from 'next/router'
import fetch from "isomorphic-unfetch"
import formatedDates from "./../../helpers/formatDates"
import Navbar from "../../components/Navbar"
import TrailerVideo from "../../components/TrailerVideo"
import Characters from "./../../components/Characters"
import Categories from "../../components/Categories"
import Streamers from "../../components/Streamers"
import Reviews from "../../components/Reviews"
const Post = ({ anime, animeCharacters, categories, streaming, reviews}) => {
const [readMore, setReadMore] = useState(false)
const handleReadMore = () => setReadMore((prevState) => !prevState)
let {
titles: { en, ja_jp },
synopsis,
startDate,
endDate,
ageRating,
ageRatingGuide,
averageRating,
episodeCount,
posterImage: { small },
coverImage,
youtubeVideoId,
} = anime.data.attributes
const defaultImg = "/cover-img-default.jpg"
const synopsisSubString = () =>
!readMore ? synopsis.substring(0, 240) : synopsis.substring(0, 2000)
const router = useRouter()
if(router.isFallback) return <div>loading...</div>
return (
<div className='relative'>
<div className='z-0'>
<img
className='absolute mb-4 h-12 min-h-230 w-full object-cover opacity-50'
src={!coverImage ? defaultImg : coverImage.large}
/>
</div>
<div className='relative container z-50'>
<Navbar />
<div className='mt-16 flex flex-wrap md:flex-no-wrap'>
{/* Main */}
<div className='md:max-w-284'>
<img className='z-50 mb-6' src={small} />
<div className='xl:text-lg pb-6'>
<h1 className='mb-2'>Anime Details</h1>
<ul>
<li>
<span className='font-bold'>Japanese Title:</span> {ja_jp}
</li>
<li>
<span className='font-bold'>Aired:</span>{" "}
{formatedDates(startDate, endDate)}
</li>
<li>
<span className='font-bold'>Rating:</span> {ageRating} /{" "}
{ageRatingGuide}
</li>
<li>
<span className='font-bold'>Episodes:</span> {episodeCount}
</li>
</ul>
</div>
<Streamers streaming={streaming} />
</div>
{/* Info Section */}
<div className='flex flex-wrap lg:flex-no-wrap md:flex-1 '>
<div className='mt-6 md:mt-40 md:ml-6 lg:mr-10'>
<h1 className='sm:text-3xl pb-1'>{en}</h1>
<h2 className='sm:text-xl lg:text-2xl pb-4 text-yellow-600'>
{averageRating}{" "}
<span className='text-white text-base lg:text-lg'>
Community Rating
</span>
</h2>
<div>
<p className='max-w-2xl pb-3 overflow-hidden xl:text-lg'>
{synopsisSubString()}
<span className={!readMore ? "inline" : "hidden"}>...</span>
</p>
<button
className='text-teal-500 hover:text-teal-900 transition ease-in-out duration-500 focus:outline-none focus:shadow-outline'
onClick={handleReadMore}
>
{!readMore ? "Read More" : "Read Less"}
</button>
</div>
<Categories categories={categories} />
<Reviews reviews={reviews}/>
</div>
{/* Sidebar */}
<section className='lg:max-w-sm mt-10 md:ml-6 lg:ml-0'>
<TrailerVideo youtubeVideoId={youtubeVideoId} />
<Characters animeCharacters={animeCharacters} />
</section>
</div>
</div>
</div>
</div>
)
}
export const getStaticProps = async ({ params }) => {
const [anime, animeCharacters, categories, streaming, reviews] = await Promise.all([
fetch(`https://kitsu.io/api/edge/anime/${params.id}`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/characters`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/categories`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/streaming-links`),
fetch(`https://kitsu.io/api/edge/anime/${params.id}/reviews`),
])
.then((responses) =>
Promise.all(responses.map((response) => response.json()))
)
.catch((e) => console.log(e, "There was an error retrieving the data"))
return { props: { anime, animeCharacters, categories, streaming, reviews } }
}
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
return { paths, fallback: true }
}
export default Post
错误截图
最佳答案
如果您使用的 API 以 10 个为一组提供资源,那么当您在 getStaticPaths
中调用 API 时,您只能提前获得 10 个 id
。在 nextjs 中使用静态生成可以在生产模式下提前为所有可用的 id 构建静态页面。但是,在开发模式下,您的服务器将根据每个请求重新创建每个页面。因此,要解决这个问题,您可以构建前 10 个页面,并将其余页面设为后备页面。以下是具体操作方法。
export const getStaticPaths = async () => {
const res = await fetch("https://kitsu.io/api/edge/anime")
const anime = await res.json()
// you can make a series of calls to the API requesting
// the next page to get the desired amount of data (100 or 1000)
// how many ever static pages you want to build ahead of time
const paths = anime.data.map((show) => ({
params: { id: show.id },
}))
// this will generate 10(resource limit if you make 1 call because your API returns only 10 resources)
// pages ahead of time and rest of the pages will be fallback
return { paths, fallback: true }
}
请记住,在 getStaticPaths
中使用 {fallback: true}
时,您需要某种加载指示器,因为当您发出请求时,页面将静态生成第一次需要一些时间(通常很快)。
在您想要静态生成的页面中
function MyPage = (props) {
const router = useRouter()
if (router.isFallback) {
// your loading indicator
return <div>loading...</div>
}
return (
// the normal logic for your page
)
}
附注我忘记提及如何处理 API 响应 404 或 500 且在静态生成中使用后备时不存在可作为 props 发送的资源的错误。
以下是具体操作方法。
const getStaticProps = async () => {
// your data fetching logic
// if fail
return {
props: {data: null, error: true, statusCode: 'the-status-code-you-want-to-send-(500 or 404)'}
}
// if success
return {
props: {data: 'my-fetched-data', error: false}
}
}
// in the page component
import ErrorPage from 'next/error';
function MyStaticPage(props) {
if (props.error) {
return <ErrorPage statusCode={404}/>
}
// else you normal page logic
}
请告诉我它是否有帮助或者您在实现时遇到了一些错误。 您可以在这里了解更多https://nextjs.org/docs/basic-features/data-fetching#getstaticpaths-static-generation
关于javascript - 下一个 JS 构建并不是构建每条路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61941296/