如何解决Contentful与Next.js App Router集成时无法获取内容的问题
问题:Next.js App Router集成Contentful无法获取内容,返回400错误
错误详情
错误触发于src\app\utils\getContent.jsx第9行调用client.getEntries时,具体返回信息:
Error: {
"status": 400,
"statusText": "Bad Request",
"message": "The query you sent was invalid. Probably a filter or ordering specification is not applicable to the type of a field.",
"details": {
"errors": [
{
"name": "unknownContentType",
"value": "DOESNOTEXIST"
}
]
}
}
相关代码
1. resources/[slug]/Page.jsx
import Button from "../components/Button/Button"; import { createClient } from "contentful"; import Image from "next/image"; import Link from "next/link"; import { GoArrowLeft } from "react-icons/go"; const client = createClient({ space: process.env.CONTENTFUL_SPACE_ID, accessToken: process.env.CONTENTFUL_ACCESS_KEY, }); export async function generateMetaData({ params }) { try { const resource = await fetchResource(params); if (!resource) return { title: "404 Not Found", description: "The resource you are looking for does not exist or it has been removed.", }; return { title: resource.fields.title, description: resource.fields.description, }; } catch (error) { console.error(error); return { title: "404 Not Found", description: "The resource you are looking for does not exist or it has been removed.", }; } } export async function generateStaticParams() { const res = await client.getEntries({ content_type: "resource", }); return res.items.map((item) => ({ slug: item.fields.slug, })); } async function fetchResource({ slug }) { const res = await client.getEntries( { content_type: "resource", "fields.slug": slug, }, { revalidate: 345600, // Add revalidation here } ); return res.items[0]; } export default async function ResourceDetails({ params }) { const resource = await fetchResource(params); return ( <section className="mt-8"> <Link scroll={false} href="/" className="flex items-center gap-x-1 pl-2"> <GoArrowLeft color="#F7F7F7" size={24} /> <span className=" font-semibold text-text text-base">Back</span> </Link> <div className="flex flex-col md:flex-row mt-5 gap-x-10 lg:gap-x-16 justify-center items-center"> <div className=" bg-super-dark-gray flex relative items-center justify-center md:pb-[25%] md:pt-[25%] pt-[50%] pb-[50%] rounded-2xl w-full md:w-1/2 border-4 border-outline border-opacity-15"> <div className="absolute w-2/3 rounded-3xl overflow-hidden shadow-shine bg-transparent bg-opacity-0 "> <Image priority={true} alt={resource.fields.title} src={"https:" + resource.fields.thumbnail.fields.file.url} className="h-full w-full" width={800} height={800} /> </div> </div> <div className="w-full mt-12 md:mt-0 md:w-1/2"> <div className="flex flex-col gap-y-3 items-start"> <h1 className="text-h4 xl:text-h3 font-bold"> {resource.fields.title} </h1> <p className=" text-text text-base xl:text-h6 2xl:text-h5 max-w-[50ch] text-balance pb-3 "> {resource.fields.description} </p> <Button target="_blank" rel="noopener noreferrer" href={resource.fields.link} > View Source </Button> </div> <div className="flex flex-col gap-x-2 mt-12 divide-y-2 divide-outline divide-opacity-20 text-xs xl:text-base 2xl:text-h6"> <div className=" gap-x-1 grid grid-cols-12 border-t-2 border-outline border-opacity-20 py-2"> <h2 className=" font-semibold col-span-4">Category</h2> <span className=" col-span-8 text-text px-1"> {resource.fields.category.fields.category} </span> </div> <div className=" gap-x-1 grid grid-cols-12 "> <h2 className=" font-semibold col-span-4 pt-2">Tags</h2> <span className="flex flex-col col-span-8 text-text"> {resource.fields.tags.map((tag) => ( <span className=" py-2 border-b-2 border-outline border-opacity-20 px-1" key={tag.sys.id} > {tag.fields.tag} </span> ))} </span> </div> </div> </div> </div> </section> ); }
2. src/app/utils/getContent.jsx
import { createClient } from "contentful"; export async function getContent({ contentType, ...rest }) { const client = createClient({ space: process.env.CONTENTFUL_SPACE_ID, accessToken: process.env.CONTENTFUL_ACCESS_TOKEN, }); const { items, total } = await client.getEntries({ content_type: contentType, ...rest, }); return { items, total }; }
问题诊断
错误明确显示请求传入的content_type值为DOESNOTEXIST,但你在Page.jsx中调用的是content_type: "resource",说明项目中存在其他调用getContent的地方,传入了无效的contentType参数,而非你预期的合法内容类型ID。
解决方案
- 排查
getContent的所有调用:全局搜索项目中使用getContent的代码,检查每个调用传入的contentType值是否为Contentful后台已存在的内容类型ID(比如你的resource类型),替换掉DOESNOTEXIST这类无效值。 - 确认内容类型ID正确性:登录Contentful后台,进入对应内容类型的设置页面,核实其ID确实为
resource(注意ID是系统生成的唯一标识,不是显示名称)。 - 统一环境变量名:注意到
Page.jsx中使用的是CONTENTFUL_ACCESS_KEY,而getContent.jsx中用的是CONTENTFUL_ACCESS_TOKEN,需确保这两个环境变量指向同一个有效令牌,或统一为同一个变量名避免混淆。 - 添加参数校验:在
getContent函数中增加参数校验,提前拦截无效输入:
export async function getContent({ contentType, ...rest }) { if (!contentType || typeof contentType !== 'string') { throw new Error('必须传入有效的contentType参数'); } // 原有客户端创建及请求逻辑 }
内容的提问来源于stack exchange,提问作者Janani
相关产品推荐
相关产品推荐

