Next.js 13 + TypeScript构建时出现Type is not assignable to type 'never'错误
问题场景
构建基于Next.js 13、TypeScript和ESLint的项目时,控制台抛出如下类型错误:
.next/types/app/facebook/page.ts:8:13
Type error: Type 'OmitWithTag<typeof import("D:/projects/abkh24/client/app/facebook/page"), "metadata" | "default" | "config" | "generateStaticParams" | "revalidate" | "dynamic" | "dynamicParams" | "fetchCache" | "preferredRegion" | "runtime" | "maxDuration" | "generateMetadata", "">' does not satisfy the constraint '{ [x: string]: never; }'.
Property 'fetchUser' is incompatible with index signature.
Type '(id: string, token: string) => Promise<UserData | null>' is not assignable to type 'never'.6 |
7 | // Check that the entry is a valid entry8 | checkFields<Diff<{
| ^
9 | default: Function
10 | config?: {}
11 | generateStaticParams?: Function
error Command failed with exit code 1.
错误涉及包含fetchUser函数的页面组件,该函数用于调用Facebook Graph API获取用户信息,组件代码如下:
"use client"; import { Box } from "@mui/material"; import React, { useState, useEffect, Fragment } from "react"; import Spinner from "@/components/Spinner"; import { useIsMount } from "@/misc/tools"; import { useAppDispatch, useAppSelector } from "@/store/hooks"; import { setAuth } from "@/store/authSlice"; import { RootState } from "@/store"; import { redirect } from "next/navigation"; const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN; type UserData = { avatar: string; category: string; name: string; }; function stringBetweenStrings( startStr: string, endStr: string, str: string, ): string { const pos = str.indexOf(startStr) + startStr.length; return str.substring(pos, str.indexOf(endStr, pos)); } export const fetchUser = async ( id: string, token: string, ): Promise<UserData | null> => { try { const response = await fetch( `https://graph.facebook.com/${id}?fields=name,picture&access_token=${token}`, ); const data = await response.json(); return { avatar: data.picture.data.url, category: "facebook", name: data.name, }; } catch (error) { console.error("Error fetching user data", error); return null; } }; export default function page() { const { name, avatar, category } = useAppSelector( (state: RootState) => state.auth, ); const dispatch = useAppDispatch(); const isMount = useIsMount(); const [facebookId, setFacebookId] = useState<string>(''); useEffect(() => { const myUrl = window.location.href; setFacebookId(stringBetweenStrings("id=", "#_=_", myUrl)); }, []); useEffect(() => { (async () => { if (!isMount && ACCESS_TOKEN) { const data: UserData | null = await fetchUser(facebookId, ACCESS_TOKEN); dispatch(setAuth({ ...data, id: facebookId })); } })(); }, [facebookId]); useEffect(() => { if (!isMount && category) { redirect("/"); } }, [category]); return ( <Fragment> <Box sx={{ position: "fixed", zIndex: 111, top: 0, left: 0, height: "100vh", width: "100%", display: "flex", justifyContent: "center", alignItems: "center", background: "#fff", }} ></Box> <Spinner /> </Fragment> ); }
问题原因
Next.js 13的App Router对页面文件的导出有严格限制:页面文件仅允许导出default组件、metadata、generateMetadata、generateStaticParams等官方指定内容,不能导出自定义函数(如这里的fetchUser)。非规范导出会触发类型检查报错。
解决方法
方法1:将函数改为文件私有函数
移除fetchUser和stringBetweenStrings的export关键字,让它们成为页面文件内部的私有函数:
// 移除export,改为私有函数 const fetchUser = async ( id: string, token: string, ): Promise<UserData | null> => { // 函数逻辑保持不变 }; const stringBetweenStrings = ( startStr: string, endStr: string, str: string, ): string => { // 函数逻辑保持不变 };
方法2:抽离函数到工具文件
若函数需要复用,将其移至独立工具文件(如@/utils/facebook.ts),再在页面组件中导入:
- 创建
@/utils/facebook.ts:
export type UserData = { avatar: string; category: string; name: string; }; export const fetchUser = async ( id: string, token: string, ): Promise<UserData | null> => { try { const response = await fetch( `https://graph.facebook.com/${id}?fields=name,picture&access_token=${token}`, ); const data = await response.json(); return { avatar: data.picture.data.url, category: "facebook", name: data.name, }; } catch (error) { console.error("Error fetching user data", error); return null; } }; export const stringBetweenStrings = ( startStr: string, endStr: string, str: string, ): string => { const pos = str.indexOf(startStr) + startStr.length; return str.substring(pos, str.indexOf(endStr, pos)); };
- 在页面组件中导入:
"use client"; import { Box } from "@mui/material"; import React, { useState, useEffect, Fragment } from "react"; import Spinner from "@/components/Spinner"; import { useIsMount } from "@/misc/tools"; import { useAppDispatch, useAppSelector } from "@/store/hooks"; import { setAuth } from "@/store/authSlice"; import { RootState } from "@/store"; import { redirect } from "next/navigation"; // 导入工具函数 import { fetchUser, stringBetweenStrings, UserData } from "@/utils/facebook"; const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN; export default function page() { // 组件逻辑保持不变 }
修改后页面文件仅保留规范导出内容,类型检查将通过,构建错误消失。
内容的提问来源于stack exchange,提问作者user11553898

