Vercel部署踩坑系列1:更严格的类型判断
11/25/2025, 3:24:11 PM
#Next.js#Vercel#TypeScript
之前就打算把项目部署到Vercel上去,因为项目是用Next写的,也算是Vercel的亲儿子了。而Vercel本身具有免运维、自动化构建和全球 CDN 等优点,应该、似乎、也许只要我点点小手就行了吧?然而真是太年轻,一踩一个坑。特此记录。
首先,在我本地无数次运行类型检查并成功构建以后,我万万没想到第一次错误就是类型错误。正当信心满满点击部署,结果没到一分钟,Vercel跳出来一个Type error:
▲ Next.js 15.5.6
Creating an optimized production build ...
✓ Compiled successfully in 33.5s
Linting and checking validity of types ...
Type error: Parameter 'post' implicitly has an 'any' type.
注意看前面几行,已经build成功了,但是不准用any类型,还细心地贴出来是哪个参数哪一段代码。此时的我还不以为然,改个类型多快啊。
几分钟后,又跳出来这段错误,这次换了个参数......
好了这下尴尬了,代码写了那么多,很多地方TS没有警告的话也不会注意是不是any,而Vercel又比平时的 tsc --noEmit 更严格。接下来只能自己批量检查一遍了。
1、配置VS Code工具:
在自己的setting.json文件里面加上这些配置,或者单独此项目,建立一个.vscode文件夹,里面放入setting.json:
{
// TypeScript 配置
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
// 实时显示 TypeScript 错误
"typescript.validate.enable": true,
"typescript.suggestionActions.enabled": true,
// ESLint 配置 - 实时检查
"eslint.enable": true,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"eslint.run": "onType",
// 保存时自动修复(可选)
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
// 显示所有问题
"problems.showCurrentInStatus": true,
// TypeScript 严格模式
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
// 编辑器配置
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
}
2、加入更严格TS类型判断和Eslint检查:
首先是eslint.config.mjs文件,核心代码如下:
const eslintConfig = [
// Next 官方推荐基础规则
...compat.extends('next/core-web-vitals', 'next/typescript'),
// 仅对 TS/TSX 启用需要类型信息的规则,避免对 JS/配置文件报错
{
rules: {
// 与 Vercel 构建严格度对齐
'@typescript-eslint/no-explicit-any': 'error',
// 这些为类型感知规则,可发现潜在的隐患
// 对于已添加注释说明的 any 使用,允许通过 eslint-disable 抑制警告
'@typescript-eslint/no-unsafe-assignment': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
},
},
];
然后是tsconfig.json:
// 严格模式选项 - 确保与 Next.js 构建一致
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// 额外检查
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
3、写个脚本查漏补缺和批量检查:
import { execSync } from 'child_process';
import { readFileSync } from 'fs';
console.log('扫描可能的隐式 any 类型错误\n');
const patterns = [
{
name: '回调函数参数未标注类型',
regex: /\.(map|filter|forEach|reduce|find|some|every)\(\s*\((\w+)\)\s*=>/g,
description: '例如: .map((item) => ...) 应该改为 .map((item: Type) => ...)',
},
{
name: 'Object.entries 未标注类型',
regex: /Object\.entries\([^)]+\)\.forEach\(\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=>/g,
description: '例如: Object.entries(obj).forEach(([k, v]) => ...) 应该标注类型',
},
{
name: 'Array.from 回调未标注类型',
regex: /Array\.from\([^)]+\)\.forEach\(\s*\((\w+)\)\s*=>/g,
description: '例如: Array.from(files).forEach((file) => ...) 应该标注类型',
},
];
let totalIssues = 0;
try {
// 获取所有 TypeScript 文件
const files = execSync('find src/app -type f \\( -name "*.ts" -o -name "*.tsx" \\)', {
encoding: 'utf-8',
cwd: process.cwd(),
})
.trim()
.split('\n')
.filter(Boolean);
console.log(`扫描 ${files.length} 个文件...\n`);
for (const pattern of patterns) {
console.log(`\n ${pattern.name}`);
console.log(` ${pattern.description}\n`);
let patternIssues = 0;
for (const file of files) {
try {
const content = readFileSync(file, 'utf-8');
const matches = [...content.matchAll(pattern.regex)];
if (matches.length > 0) {
patternIssues += matches.length;
console.log(` warn: ${file}`);
// 显示匹配的代码行
const lines = content.split('\n');
matches.forEach((match) => {
const lineNum = content.substring(0, match.index).split('\n').length;
const line = lines[lineNum - 1]?.trim();
if (line) {
console.log(` 第 ${lineNum} 行: ${line.substring(0, 80)}${line.length > 80 ? '...' : ''}`);
}
});
console.log('');
}
} catch {
// 忽略无法读取的文件
}
}
if (patternIssues === 0) {
console.log('未发现问题\n');
} else {
console.log(`发现 ${patternIssues} 个潜在问题\n`);
totalIssues += patternIssues;
}
}
console.log('\n' + '='.repeat(60));
if (totalIssues === 0) {
console.log('没有发现隐式 any 类型问题。');
process.exit(0);
} else {
console.log(`共发现 ${totalIssues} 个潜在的隐式 any 类型问题。`);
process.exit(1);
}
} catch (error) {
console.error('扫描过程中出现错误:', error.message);
process.exit(1);
}
最后扫描出三十多个any隐式类型警告......
总结:
(1).不要有隐式 any 参数
// 错误
arr.map((item) => item.name);
arr.filter((tag) => tag !== '');
// 正确
arr.map((item: (typeof arr)[0]) => item.name);
arr.filter((tag: string) => tag !== '');
(2).所有回调函数参数都要加类型注解!
.map().filter().forEach().reduce().find().some().every()Object.entries 回调
未完待续。
相关系列: