#[Help] React App Not Rendering in Production Build with Splash Screen Setup

21 messages · Page 1 of 1 (latest)

lethal crypt
#

I'm building a desktop application using Tauri and React. While everything works fine in development mode, the production build gets stuck on the splash screen and the main window never renders. The backend tasks complete successfully, but the frontend doesn't seem to initialize properly.
Environment

Tauri: 2.0.0
React
macOS
Build command: yarn build

Current Setup
Tauri Configuration

{
  "build": {
    "beforeDevCommand": "yarn dev",
    "devUrl": "http://localhost:3000",
    "beforeBuildCommand": "yarn build",
    "frontendDist": "../build"
  },
  "app": {
    "windows": [
      {
        "label": "main",
        "title": "MapMap",
        "maximized": true,
        "resizable": true,
        "visible": false,
        "width": 1200,
        "height": 800,
        "center": true
      },
      {
        "label": "splashscreen",
        "title": "MapMap - Loading",
        "url": "splash.html",
        "maximized": true,
        "resizable": true,
        "alwaysOnTop": false,
        "visible": true,
        "width": 800,
        "height": 600,
        "center": true
      }
    ],
    "security": {
      "csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: ipc: http: https: ws:; connect-src 'self' ipc: http: https: data: ws:; img-src 'self' data: https: blob:; style-src 'self' 'unsafe-inline' https:; font-src 'self' data: https:; media-src 'self' https: data: blob:;",
      "devCsp": "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: ipc: http: https: ws:; connect-src 'self' ipc: http: https: data: ws:; img-src 'self' data: https: blob:; style-src 'self' 'unsafe-inline' https:; font-src 'self' data: https:; media-src 'self' https: data: blob:;"
    }
  }
}
#

Frontend Initialization (main.tsx)

async function initializeApp() {
  try {
    console.log('Configuring axios...');
    axiosInstance.defaults.baseURL = Settings[process.env.NODE_ENV].server_url;
    axiosInstance.defaults.withCredentials = true;

    console.log('Getting root element...');
    const container = document.getElementById('root');
    if (!container) {
      throw new Error('Root element not found');
    }

    console.log('Creating root...');
    const root = createRoot(container);

    console.log('Setting up React tree...');
    root.render(
      <StrictMode>
        <ErrorBoundary onError={(error, errorInfo) => logError('React rendering', error)}>
          <QueryProvider>
            <AppProvider baseURL={Settings[process.env.NODE_ENV].server_url}>
              <AuthProvider>
                <FlagProvider config={unleashConfig}>
                  <App />
                </FlagProvider>
              </AuthProvider>
            </AppProvider>
          </QueryProvider>
        </ErrorBoundary>
      </StrictMode>
    );

    await new Promise(resolve => setTimeout(resolve, 2000));
    await invoke('set_complete', { task: 'frontend' });
  } catch (error) {
    logError('initialization', error);
    await invoke('set_complete', { task: 'frontend' });
  }
}
#

Rust Window Management

#
#[tauri::command]
async fn set_complete(
    app: tauri::AppHandle,
    state: State<'_, Mutex<SetupState>>,
    task: String,
) -> Result<(), String> {
    println!("Begin set_complete for task: {}", task);
    let mut state_lock = state.lock().await;
    
    match task.as_str() {
        "frontend" => {
            if !state_lock.frontend_task {
                state_lock.frontend_task = true;
            }
        },
        "backend" => state_lock.backend_task = true,
        _ => return Err("Invalid task type".to_string()),
    }
    
    if state_lock.backend_task && state_lock.frontend_task {
        drop(state_lock);
        
        let main_window = app.get_webview_window("main")
            .ok_or("Failed to get main window".to_string())?;
        let splash_window = app.get_webview_window("splashscreen")
            .ok_or("Failed to get splash window".to_string())?;
            
        main_window.show()?;
        splash_window.close()?;
    }
    
    Ok(())
}
#

Debug Output
Running with RUST_LOG=debug RUST_BACKTRACE=full

Beginning window setup...
Getting main window...
Got main window
Getting splash window...
Got splash window
Hiding main window...
Main window hidden
Setting up splash window event handlers...
Window setup completed successfully
Begin set_complete for task: backend
State lock acquired
Current state before update - frontend: false, backend: false
Setting backend_task to true
Current state after update - frontend: false, backend: true
set_complete completed successfully for task: backend
Backend setup complete
#

What I've Tried

  1. Added comprehensive error handling and logging in both frontend and backend
  2. Configured CSP to allow necessary connections
  3. Added delays before window switching
  4. Verified that the build process completes successfully
  5. Confirmed that the backend task completes properly

Questions

  1. Why isn't the React app rendering in production while it works in development?
  2. Is there a way to debug what's happening with the main window's content?
  3. Could there be an issue with the build output path or how Tauri is serving the built files?
  4. Are there any known issues with React + Tauri regarding production builds?

Any help or guidance would be greatly appreciated!

#

folder structure

#

more code:

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    let setup_state = Mutex::new(SetupState::default());
    let storage_service = StorageService::default();

    let mut builder = tauri::Builder::default()
        .manage(setup_state)
        .manage(storage_service)
        .invoke_handler(tauri::generate_handler![
            initialize_storage,
            storage_get_string,
            storage_get_json,
            storage_set,
            set_complete,
        ])
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_notification::init())
        .plugin(tauri_plugin_fs::init());

    #[cfg(desktop)]
    {
        builder = builder.plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            let _ = app
                .get_webview_window("main")
                .expect("no main window")
                .set_focus();
        }));
    }

    builder = builder.setup(|app| {
        let app_handle = app.handle().clone();
        tauri::async_runtime::spawn(async move {
            if let Err(e) = setup(app_handle).await {
                eprintln!("Setup failed: {}", e);
            }
        });
        Ok(())
    });

    builder
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

async fn setup(app: tauri::AppHandle) -> Result<(), String> {
    setup_windows(&app).await?;

    #[cfg(desktop)]
    setup_desktop_features(&app).await?;

    // Signal that backend setup is complete
    set_complete(
        app.clone(),
        app.state::<Mutex<SetupState>>(),
        "backend".to_string(),
    )
    .await?;
    
    println!("Backend setup complete");
    Ok(())
}

#
async fn setup_windows(app: &tauri::AppHandle) -> Result<(), String> {
    println!("Beginning window setup...");
    
    println!("Getting main window...");
    let main_window = app.get_webview_window("main")
        .ok_or("Failed to get main window".to_string())?;
    println!("Got main window");

    println!("Getting splash window...");
    let splash_window = app.get_webview_window("splashscreen")
        .ok_or("Failed to get splash window".to_string())?;
    println!("Got splash window");

    println!("Hiding main window...");
    main_window.hide().map_err(|e| {
        println!("Failed to hide main window: {}", e);
        format!("Failed to hide main window: {}", e)
    })?;
    println!("Main window hidden");

    println!("Setting up splash window event handlers...");
    splash_window.on_window_event(|event| {
        match event {
            tauri::WindowEvent::CloseRequested { api, .. } => {
                println!("Splash screen close requested");
            }
            tauri::WindowEvent::Focused(focused) => {
                println!("Splash screen focused: {}", focused);
            }
            tauri::WindowEvent::Resized(size) => {
                println!("Splash screen resized to: {}x{}", size.width, size.height);
            }
            _ => {
                println!("Other splash screen event: {:?}", event);
            }
        }
    });
    println!("Window setup completed successfully");

    Ok(())
}
#
#[derive(Default)]
struct SetupState {
    frontend_task: bool,
    backend_task: bool,
}
hard umbra
#

did you also try a debug build (yarn tauri build --debug output in target/debug/) or a release build with the devtools feature flag (on the tauri dep in cargo.toml) ? Then you can check the devtools and network tabs of the empty window (if that's what you meant by not rendering) for errors etc.
The only thing that i can spot is Settings[process.env.NODE_ENV].server_url that may be problematic depending on what it set to / used for.

lethal crypt
#

Yes, I did try that

#

also I implemented a check for the env

import { FlagProvider } from '@unleash/proxy-client-react';
import 'animate.css';
import {
  AppProvider,
  AuthProvider,
  axiosInstance,
} from 'propro-common-components';
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { unleashConfig } from 'utils/unleashConfig';

import App from './app/app';

import { QueryProvider } from 'api/queryClientAlt';
import Settings from 'settings.json';
import { invoke } from '@tauri-apps/api/core';

interface ErrorBoundaryState {
  hasError: boolean;
  error: Error | null;
}

interface ErrorBoundaryProps {
  children: React.ReactNode;
}

class ErrorBoundary extends React.Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
    console.error('React Error Boundary caught an error:', error, errorInfo);
  }

  render(): React.ReactNode {
    if (this.state.hasError) {
      return (
        <div style={{ padding: 20, color: 'red' }}>
          <h1>Something went wrong.</h1>
          <pre>{this.state.error?.toString()}</pre>
        </div>
      );
    }
    return this.props.children;
  }
}

console.log('Starting app initialization...');
console.log('process.env.NODE_ENV:', process.env.NODE_ENV);
console.log('Settings:', Settings);

const container = document.getElementById('root');
if (!container) {
  throw new Error('Failed to find the root element');
}

const root = createRoot(container);

const env = process.env.NODE_ENV || 'development';
const settings = Settings[env];

if (!settings) {
  throw new Error(`No settings found for environment: ${env}`);
}
#
console.log('Using settings for environment:', env);
console.log('Server URL:', settings.server_url);

axiosInstance.defaults.baseURL = settings.server_url;
axiosInstance.defaults.withCredentials = true;

root.render(
  <StrictMode>
    <ErrorBoundary>
      <QueryProvider>
        <AppProvider baseURL={settings.server_url}>
          <AuthProvider>
            <App />
          </AuthProvider>
        </AppProvider>
      </QueryProvider>
    </ErrorBoundary>
  </StrictMode>
);

(async () => {
  try {
    await invoke('set_complete', { task: 'frontend' });
    console.log('Frontend marked as ready');
  } catch (error) {
    console.error('Failed to mark frontend as ready:', error);
  }
})();

const welcome = () => console.log('Welcome to MapMap 🚀');
welcome();
#

yet, none of the logs is showing and the page could not render

#

and there is no error logs

#

@hard umbra

hard umbra
#

is your project public by any chance? Or if not, is a reproduction repo too much to ask for? I honestly have a hard time following the code snippets here x)

lethal crypt
#

I will make a quick recording

lethal crypt
hard umbra
#

sorry for the delay! is this still an issue? I couldn't spot anything wrong in your video so i'd really appreciate if you could try to reproduce this in a new create-tauri-app project