Firebase云函数部署SSR Next.js应用时React Hooks调用报错
我之前在部署Next.js SSR到Firebase Cloud Functions时也踩过这个Hook调用的坑,结合你描述的情况,咱们来一步步排查解决:
核心问题分析
你遇到的Invariant Violation: Invalid hook call错误,结合SSR场景和Firebase部署的特点,最可能的原因是应用中存在多份React副本,其次是React与React DOM版本不匹配,Hook使用规则违反的概率相对较低(毕竟不用Hook时能正常访问)。
具体解决方案
1. 统一React/React DOM版本,排查重复依赖
首先确认你的项目根目录和Firebase Functions目录(通常是functions/)的package.json中,react和react-dom的版本都是16.10.2,并且都放在dependencies字段下(SSR环境需要生产依赖,不能放在devDependencies)。
然后执行命令查看依赖树,检查是否有重复的React包:
# 项目根目录 npm ls react react-dom # 切换到functions目录 cd functions && npm ls react react-dom
如果输出中出现多个版本的React,说明存在重复副本。
2. 强制依赖统一版本(关键步骤)
针对多份React副本的问题,我们可以通过依赖锁定来强制所有子依赖使用同一版本的React:
- 如果使用npm(8.x及以上版本):在
functions/package.json中添加overrides字段:"overrides": { "react": "$react", "react-dom": "$react-dom" } - 如果使用yarn:在
functions/package.json中添加resolutions字段:"resolutions": { "react": "16.10.2", "react-dom": "16.10.2" }
添加完成后,删除functions/node_modules和锁文件,重新安装依赖:
cd functions rm -rf node_modules package-lock.json # yarn的话删yarn.lock npm install # 或者yarn install
3. 验证Next.js SSR配置正确性
确保Firebase Functions的入口文件(比如functions/index.js)中,Next.js的初始化配置正确,特别是distDir要和Next.js的输出目录一致:
const functions = require('firebase-functions'); const next = require('next'); const dev = process.env.NODE_ENV !== 'production'; // 注意distDir配置,要匹配Next.js的默认输出目录 const app = next({ dev, conf: { distDir: '.next' } }); const handle = app.getRequestHandler(); exports.nextApp = functions.https.onRequest((req, res) => { // 确保每次请求都先准备好Next.js应用 return app.prepare().then(() => handle(req, res)); });
4. 最后确认Hook使用规则
虽然不用Hook时能正常访问,但还是快速检查pages/about.tsx中的useState是否符合规则:
- 必须在函数组件的顶级作用域调用Hook
- 不能在条件判断、循环、嵌套函数中调用Hook
正确示例:
import { useState } from 'react'; export default function About() { // 顶级作用域调用Hook,正确 const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }
补充说明
这个问题确实在firebase-functions的GitHub仓库中有相关上报,本质是Firebase Functions的依赖隔离机制可能导致Next.js的依赖和Functions自身的依赖出现冲突,生成多份React副本。按照上面的步骤强制统一版本后,基本能解决这个问题。
内容的提问来源于stack exchange,提问作者ArchNoob

