Serving static files in Electron (React app) Serving static files in Electron (React app) reactjs reactjs

Serving static files in Electron (React app)


I found another solution without using express or serve-static, we only need to cusomize Electron built-in interceptFileProtocol() to serve static contents.

Code:(main.js)

(I use the electron-quick-start as Electron template)

function createWindow () {  window = new BrowserWindow({ width: 800, height: 600 })  window.loadURL(url.format({    pathname: 'index.html',    /* Attention here: origin is path.join(__dirname, 'index.html') */    protocol: 'file',    slashes: true  }))  window.on('closed', () => {    window = null  })}app.on('ready', () => {  protocol.interceptFileProtocol('file', (request, callback) => {    const url = request.url.substr(7)    /* all urls start with 'file://' */    callback({ path: path.normalize(`${__dirname}/${url}`)})  }, (err) => {    if (err) console.error('Failed to register protocol')  })  createWindow()})

Reference:protocol.interceptFileProtocol()

Explaination:

  • Normally, if you run React app as a normal website, all static contents should be served by HTTP [GET] method. Though they use relative paths, your HTTP server will handle the path parsing work.

  • However, when running under Electron, things change.

  • Your static contents usually use relative path like ./picture.jpg, Electron will use file protocol instead of HTTP protocol and find the file under root path like C://.//. So static contents like ./picture.jpg won't be loaded correctly.

  • By customizing interceptFileProtocol(), all static contents' requests will be pointed to your working directory instead of Windows(or other OS) root.

Finally, I'm not sure whether it's a good solution for all Electron projects, but if you already have a React project (or some other SPA) and want to wrap it with Electron, this solution would be fine to use.


As an addition to the great answer from @yeze322 above, here a working sample for all not so familiar with node and electron (like me). It took me some time to find out the correct require statements.

main.js (code from @yeze322 plus required imports)

const { app, BrowserWindow, protocol } = require('electron')const path = require('path')const url = require('url')let mainWindowfunction createWindow() {  mainWindow = new BrowserWindow({ width: 800, height: 600 })  mainWindow.loadURL(url.format({    pathname: 'index.html',    /* Attention here: origin is path.join(__dirname, 'index.html') */    protocol: 'file',    slashes: true  }))  mainWindow.on('closed', function () {    mainWindow = null  })}app.on('ready', () => {  protocol.interceptFileProtocol('file', (request, callback) => {    const url = request.url.substr(7)    /* all urls start with 'file://' */    callback({ path: path.normalize(`${__dirname}/${url}`) })  }, (err) => {    if (err) console.error('Failed to register protocol')  })  createWindow()})app.on('window-all-closed', function () {  if (process.platform !== 'darwin') {    app.quit()  }})app.on('activate', function () {  if (mainWindow === null) {    createWindow()  }})


In your main file you have

const app = require("app")app.on("ready", () => {  ...

Here you can start the server like you would do in node.js

  const serveStatic = require('serve-static')  // or  const express = require('express')  ...}