on-recoverable-error.ts:30 Uncaught Error: Switched to client rendering because the server rendering errored:

window is not defined
    at module evaluation (E:\source\m-yuying-n…ef3373f._.js:1864:1)
    at instantiateModule (E:\source\m-yuying-n…k]_runtime.js:715:9)
    at getOrInstantiateModuleFromParent (E:\source\m-yuying-n…]_runtime.js:738:12)
    at Context.esmImport [as i] (E:\source\m-yuying-n…]_runtime.js:228:20)
    at module evaluation (E:\source\m-yuying-n…3373f._.js:2048:139)
    at instantiateModule (E:\source\m-yuying-n…k]_runtime.js:715:9)
    at getOrInstantiateModuleFromParent (E:\source\m-yuying-n…]_runtime.js:738:12)
    at Context.esmImport [as i] (E:\source\m-yuying-n…]_runtime.js:228:20)
    at module evaluation (E:\source\m-yuying-n…3373f._.js:2764:141)
    at instantiateModule (E:\source\m-yuying-n…k]_runtime.js:715:9)
    at getOrInstantiateModuleFromParent (E:\source\m-yuying-n…]_runtime.js:738:12)
    at Context.esmImport [as i] (E:\source\m-yuying-n…]_runtime.js:228:20)
    at module evaluation (E:\source\m-yuying-n…3373f._.js:2794:138)
    at instantiateModule (E:\source\m-yuying-n…k]_runtime.js:715:9)
    at getOrInstantiateModuleFromParent (E:\source\m-yuying-n…]_runtime.js:738:12)
    at Context.commonJsRequire [as r] (E:\source\m-yuying-n…]_runtime.js:249:12)
    at Object.require (E:\source\m-yuying-n…ime.dev.js:40:31659)
    at globalThis.__next_require__ (E:\source\m-yuying-n…ime.dev.js:40:32458)
    at requireModule (E:\source\m-yuying-n…ime.dev.js:2:336682)
    at initializeModuleChunk (E:\source\m-yuying-n…ime.dev.js:2:375329)
    at getOutlinedModel (E:\source\m-yuying-n…ime.dev.js:2:386588)
    at E:\source\m-yuying-n…ime.dev.js:2:395211
    at ResponseInstance._fromJSON (E:\source\m-yuying-n…ime.dev.js:2:395302)
    at JSON.parse (<anonymous>)
    at initializeModelChunk (E:\source\m-yuying-n…ime.dev.js:2:374523)
    at resolveModelChunk (E:\source\m-yuying-n…ime.dev.js:2:372866)
    at processFullStringRow (E:\source\m-yuying-n…ime.dev.js:2:427649)
    at E:\source\m-yuying-n…ime.dev.js:2:430328
    at processBinaryChunk (E:\source\m-yuying-n…ime.dev.js:2:430412)
    at progress (E:\source\m-yuying-n…ime.dev.js:2:432161)
<Suspense>		
Index	@	Index.tsx:20
"use client"		
RootLayout	@	layout.tsx:36

总结修复内容

我在 common.js 中修复了所有在模块级别或拦截器中使用浏览器 API 的地方:

修复的关键问题:

  1. 模块级别的 window 访问(第17-46行)

    • 原来:直接使用 window !== window.parent
    • 修复:添加 if (typeof window !== 'undefined') 检查
  2. 请求拦截器中的浏览器 API(第48-89行)

    • 原来:直接使用 localStorage、window.platform、document.location
    • 修复:在使用前添加 typeof window !== 'undefined' 和 typeof localStorage !== 'undefined' 检查
  3. 响应拦截器中的浏览器 API(第128-194行)

    • 原来:直接使用 localStorage.removeItem()、window.location.reload()、document.location.href
    • 修复:所有浏览器 API 调用都添加了类型检查

为什么会出现这个问题?

Next.js 使用服务器端渲染(SSR)。即使组件标记为 'use client',在页面刷新时,Next.js 仍会在服务器端进行初始渲染。当 api/common.js 被导入时,其中的代码会在服务器端执行,但服务器端(Node.js)没有 window、document、localStorage 等浏览器专属的全局对象,因此会报错。

现在这些代码只会在浏览器环境中执行,服务器端渲染时会安全地跳过这些代码。

import axios from 'axios'
import { message } from 'antd'
// import { baseURL } from '@/utils/config'
import qs from 'qs'
import { showLoading, hideLoading } from '@/utils/tool'

let isCanShow = true
const service = axios.create()
const baseURL = process.env.NEXT_PUBLIC_API_BASE_URL || ''

service.defaults.baseURL = baseURL

let isInIframe = false

let msgToChild = {}

if (typeof window !== 'undefined') {
  if (window !== window.parent) {
    isInIframe = true

    //跨域
    // if (window.parent) {
    //   console.log(666, window?.parent)
    //   if (window?.parent?.isCrx && window?.parent?.crxVersion) {
    //     msgToChild = {
    //       isCrx: window.parent.isCrx,
    //       crxVersion: window.parent.crxVersion
    //     }
    //   }
    // }
  }

  window.addEventListener(
    'message',
    function (event) {
      if (event.data) {
        try {
          msgToChild = JSON.parse(event.data)
        } catch (error) {
          msgToChild = {}
        }
      }
    },
    false
  )
}

service.interceptors.request.use(
  (config) => {
    //不同的平台提供不同的token和loginUserId

    if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {
      if (config.isNotNeedToken !== true) {
        config.headers.authorization = localStorage.getItem('token')
      }
      if (window.platform === 'rn') {
        config.headers.platformos = localStorage.getItem('platformos')
          ? localStorage.getItem('platformos')
          : 'rn'
        config.headers.version = localStorage.getItem('appVersion')
          ? localStorage.getItem('appVersion')
          : ''
      } else if (isInIframe && msgToChild.isCrx) {
        config.headers.platformos = 'h5'
        config.headers.version = window.version
        config.headers.isCrx = msgToChild.isCrx
        config.headers.crxVersion = msgToChild.crxVersion
      } else {
        config.headers.platformos = 'h5'
        config.headers.version = window.version
      }
    }
    if (typeof document !== 'undefined') {
      config.headers.href = `${document.location.href}?platformos=${config.headers.platformos || 'h5'}&version=${config.headers.version || ''}`
    }
    config.timeout = 30 * 60 * 1000

    // 请求接口时显示loading,接口响应后隐藏loading,如果有特殊情况不能满足需求的,例如同时请求了多个接口
    // 且接口响应时间有比较大的差异,loading的显示隐藏状态不能友好的展示,可以直接在业务代码或api层,把
    // isLoading设置为false,则不再使用拦截器控制loading的状态,自己在业务代码里手动控制loading的状态
    if (config.isLoading !== false) {
      showLoading()
    }
    return config
  },
  (err) => {
    return Promise.reject(err)
  }
)

service.interceptors.response.use(
  (res) => {
    if (res.config.isLoading !== false) {
      setTimeout(() => {
        hideLoading()
      }, 500)
    }
    if (res.config.allResponse) {
      return res
    } else {
      //code是node接口的状态码,state是java接口的状态码
      if (res.data.code === 200 || res.data.state === 1) {
        return res.data
      } else if (
        res.data.code === 400 ||
        res.data.code === 500 ||
        res.data.state === 0
      ) {
        let msg = ''
        msg = res.data.message
        if (res.data && res.data.data) {
          msg += res.data.data.error_msg ? `:${res.data.data.error_msg} ` : ''
          msg += res.data.data.error_code ? res.data.data.error_code : ''
        }
        if (res.config.isShowMessage !== false) {
          if (res.data?.data?.isLong) {
            message.open({
              type: 'success',
              content: msg,
              duration: 30,
            })
          } else {
            message.warning(msg)
          }
        }
        //Promise.reject(res)
        return res.data
      } else if (res.data.code === 403) {
        if (typeof localStorage !== 'undefined') {
          localStorage.removeItem('token')
        }
        if (isCanShow) {
          message.warning(res.data.message)
          isCanShow = false
        }
        setTimeout(() => {
          isCanShow = true
        }, 2000)
        if (typeof window !== 'undefined') {
          if (window.platform === 'rn') {
            message.warning('请重新登录')
          } else if (typeof document !== 'undefined') {
            if (document.location.href.includes('#/h5')) {
              window.reactRouter?.replace({ pathname: '/ai/login' })
            } else if (document.location.href.includes('#/light')) {
              window.reactRouter?.replace({ pathname: '/light/login' })
            } else if (document.location.href.includes('#/ai')) {
              window.reactRouter?.replace({ pathname: '/welcome/home' })
            } else {
              //window.reactRouter.replace({ pathname: '/welcome/home' })
            }
          }
        }

        return res.data
      } else if (res.data.code === 40001) {
        // if (isCanShow) {
        //   message.warning(res.data.message)
        //   isCanShow = false
        // }
        // setTimeout(() => {
        //   isCanShow = true
        // }, 1000)
        // if (document.location.href.includes('#/h5')) {
        //   window.reactRouter.replace({ pathname: '/h5/single/me/exchange' })
        // }

        return res.data
      } else if (res.data.code === 40002) {
        if (isCanShow && typeof localStorage !== 'undefined') {
          isCanShow = false
          const reloadTime = Date.now()
          const historyReloadTime = localStorage.getItem('reloadTime')
          if (historyReloadTime) {
            if (reloadTime - historyReloadTime >= 60 * 1000) {
              message.warning(res.data.message)
              localStorage.setItem('reloadTime', reloadTime)
              if (typeof window !== 'undefined') {
                window.location.reload()
              }
            }
          } else {
            message.warning(res.data.message)
            localStorage.setItem('reloadTime', reloadTime)
            if (typeof window !== 'undefined') {
              window.location.reload()
            }
          }
        }
        setTimeout(() => {
          isCanShow = true
        }, 1000)

        return res.data
      } else if (res.data.code === 40003) {
        return res.data
      } else if (res.data.code === 40006) {
        return res.data
      } else {
        return Promise.reject(res)
      }
    }
  },
  (err) => {
    hideLoading()
    let { response } = err
    if (typeof response === 'undefined') {
      return Promise.reject(err)
    }

    if (response) {
      let { status } = response
      if (status === 401) {
      } else {

      }
    } else {

    }
    return Promise.reject(err)
  }
)

export const common = async (config) => {
  if (config.contentType === 'application/x-www-form-urlencoded') {
    config.headers = { 'content-type': 'application/x-www-form-urlencoded' }
    config.data = qs.stringify(config.data)
  }
  let res = await service(config)
  return res
}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐