You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Electron-Vite+React构建VPN应用后出现白屏问题求助

问题:Electron+React VPN应用构建后白屏,集成API后出现

开发环境运行完全正常,但构建完成后出现白屏问题,且在集成服务器API后出现该现象。

控制台截图:
Console screen

网络标签页截图:
Network tab


electron-builder.yml 配置

appId: com.9D-VPN.app
productName: 9D-VPN
directories:
  buildResources: build
files:
  - '!**/.vscode/*'
  - '!src/*'
  - '!electron.vite.config.{js,ts,mjs,cjs}'
  - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
  - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
asarUnpack:
  - resources/**
win:
  executableName: 9D-VPN
  icon: 'resources/icon.png'
nsis:
  artifactName: ${name}-${version}.${ext}
  shortcutName: ${productName}
  uninstallDisplayName: ${productName}
  createDesktopShortcut: always
mac:
  entitlementsInherit: build/entitlements.mac.plist
  extendInfo:
    - NSCameraUsageDescription: Application requests access to the device's camera.
    - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
    - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
    - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
  notarize: false
dmg:
  artifactName: ${name}-${version}.${ext}
linux:
  target:
    - AppImage
    - snap
    - deb
  maintainer: 9dvpn.io
  category: Utility
appImage:
  artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
  provider: generic
  url: https://example.com/auto-updates

React 渲染进程代码(app.js)

function App() {
  return (
    <Router>
      <ModalProvider>
        <ServersProvider>
          <Routes>
            <Route path="/" element={<AppRoutes />} />
            <Route path="/profile" element={<Profile />} />
            <Route path="/setting" element={<Setting />} />
            <Route path="/feedback" element={<FeedBack />} />
            <Route path="/help" element={<Help />} />
            <Route path="/suggestion" element={<Suggestion />} />
            <Route path="/sign-in" element={<SignIn />} />
            <Route path="/sign-up" element={<SignUp />} />
            <Route path="/forget-password" element={<Forget />} />
          </Routes>
          <PricingModal />
        </ServersProvider>
      </ModalProvider>
    </Router>
  )
}

const AppRoutes = () => {
  const { loading, servers } = useServers()

  return loading ? <Splash /> : <Home servers={servers} />
}

export default App

Electron 主进程代码(main.js)

import { app, shell, BrowserWindow, ipcMain, Notification } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'

function createWindow() {
  const mainWindow = new BrowserWindow({
    width: 1000,
    height: 800,
    minWidth: 1000,
    minHeight: 800,
    maxWidth: 1000,
    maxHeight: 800,
    show: false,
    autoHideMenuBar: true,
    frame: true,
    icon: join(__dirname, '../renderer/src/assets/9dicon.ico'),
    webPreferences: {
      preload: join(__dirname, '../preload/index.js'),
      sandbox: false,
      contextIsolation: true,
      nodeIntegration: false
    }
  })

  mainWindow.webContents.openDevTools() // Optionally open the DevTools console

  mainWindow.on('ready-to-show', () => {
    mainWindow.show()
    showOnboardingNotification() // Show notification when window is ready
  })

  mainWindow.webContents.setWindowOpenHandler((details) => {
    shell.openExternal(details.url)
    return { action: 'deny' }
  })

  if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
    mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
  } else {
    mainWindow.loadFile(join(__dirname, '../../out/renderer/index.html'))
  }
}

function showOnboardingNotification() {
  // Ensure to have a notification icon for better UX
  const notificationIcon = join(__dirname, '../renderer/src/assets/notificationIcon.png')
  const notification = new Notification({
    title: 'Welcome to 9D VPN',
    body: 'Thank you for installing. Let’s get started!',
    icon: notificationIcon
  })

  notification.show()
}

app.whenReady().then(() => {
  electronApp.setAppUserModelId('com.electron') // Make sure to replace 'com.electron' with your actual app ID
  app.on('browser-window-created', (_, window) => optimizer.watchWindowShortcuts(window))
  ipcMain.on('ping', () => console.log('pong'))
  createWindow()
})

app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit()
})

排查与解决步骤

1. 处理CORS跨域问题

从控制台截图看存在CORS错误,生产环境Electron加载本地文件时,前端直接请求API会触发跨域限制:

  • 临时排查:在主进程的BrowserWindow配置中添加webSecurity: false(正式环境不建议长期使用):
    webPreferences: {
      // ...其他配置
      webSecurity: false
    }
    
  • 正式方案:通过主进程代理API请求,在preload中暴露IPC方法,主进程用fetch请求API后将结果返回给渲染进程,避免前端直接跨域请求。

2. 修复环境变量缺失问题

你在electron-builder配置中排除了所有.env文件,导致生产环境没有API地址,请求失败:

  • 解决:构建前将API地址注入打包产物,比如用Vite的define配置;或修改electron-builder.yml,保留.env.production等生产环境配置文件;临时测试可直接在代码中硬编码API地址。

3. 修正资源加载路径

网络截图显示有资源加载失败,大概率是打包后路径错误:

  • 确保React打包时设置base: './'(Vite配置),因为Electron加载本地文件需用相对路径。
  • 检查主进程中index.html的加载路径:join(__dirname, '../../out/renderer/index.html'),确认打包后该路径真实存在。

4. 完善useServers的错误处理

如果API请求失败后loading状态一直为true,会导致页面一直卡在白屏/启动页:

  • 给useServers添加错误捕获与状态重置:
    const useServers = () => {
      const [loading, setLoading] = useState(true)
      const [servers, setServers] = useState([])
      const [error, setError] = useState(null)
    
      useEffect(() => {
        fetch('/api/servers')
          .then(res => res.json())
          .then(data => setServers(data))
          .catch(err => setError(err))
          .finally(() => setLoading(false))
      }, [])
    
      return { loading, servers, error }
    }
    
  • 在AppRoutes中处理错误状态,避免无限加载:
    const AppRoutes = () => {
      const { loading, servers, error } = useServers()
    
      if (loading) return <Splash />
      if (error) return <div className="error">加载服务器失败:{error.message}</div>
      return <Home servers={servers} />
    }
    

5. 检查asar打包影响

如果API依赖的本地资源(如证书)被打包进asar文件无法访问,需将相关路径加入asarUnpack配置:

asarUnpack:
  - resources/**
  - path/to/your/api/resources/**

内容的提问来源于stack exchange,提问作者Haider Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:05:32