Electron-Vite+React构建VPN应用后出现白屏问题求助
问题:Electron+React VPN应用构建后白屏,集成API后出现
开发环境运行完全正常,但构建完成后出现白屏问题,且在集成服务器API后出现该现象。
控制台截图:
网络标签页截图:
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
相关产品推荐
相关产品推荐

