Preparing Your Expo Web Game
Expo is a framework and platform for universal React applications. You can use it to build games that run on web, iOS, and Android from a single codebase. Our platform supports Expo web builds.
Set Up Your Expo Project
If you haven't already, create a new Expo project:
# Install the Expo CLI yarn global add expo-cli # Create a new Expo project expo init my-game # Choose a template (e.g., blank, blank (TypeScript), or tabs) # Navigate to your project cd my-game
Note: We recommend using yarn as your package manager for Expo projects.
Configure Your Project for Web
Ensure your project is set up for web development:
# Install web dependencies yarn add react-native-web react-dom @expo/webpack-config
Update your app.json
to include web configuration:
{ "expo": { "name": "My Game", "slug": "my-game", "version": "1.0.0", "orientation": "portrait", "platforms": ["ios", "android", "web"], "web": { "favicon": "./assets/favicon.png" }, // ... other configurations } }
Develop Your Game
Create your game using React Native components. Here's a simple example of a game component:
import React, { useState, useEffect } from 'react'; import { StyleSheet, View, Text, TouchableOpacity } from 'react-native'; export default function Game() { const [score, setScore] = useState(0); const handlePress = () => { setScore(score + 1); }; return ( <View style={styles.container}> <Text style={styles.title}>My Expo Game</Text> <Text style={styles.score}>Score: {score}</Text> <TouchableOpacity style={styles.button} onPress={handlePress}> <Text style={styles.buttonText}>Click Me!</Text> </TouchableOpacity> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#f0f0f0', }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, score: { fontSize: 18, marginBottom: 30, }, button: { backgroundColor: '#4a6cf7', padding: 15, borderRadius: 8, }, buttonText: { color: 'white', fontSize: 16, fontWeight: 'bold', }, });
Test Your Game in Web Mode
Run your game in web mode to test it:
# Start the web development server yarn web
This will open your game in a web browser. Test all features to ensure they work correctly in the web environment.
Build Your Game for Web
Create a production build of your game for web:
# Build for web expo build:web
This will create a web-build
directory with the following structure:
web-build/ ├── _expo/ ├── assets/ ├── index.html ├── not-found.html ├── _sitemap.html └── ...
Note: The _expo
folder contains Expo-specific files needed for your game to run correctly.
Test Your Production Build
Before uploading, test your production build to ensure everything works correctly:
# Using a simple HTTP server npx serve web-build
Check that all game features, assets, and interactions work as expected.
Zip Your Build
Compress your web-build directory into a ZIP file:
# On macOS/Linux cd web-build zip -r ../my-game.zip ./* # On Windows # Right-click the web-build folder > Send to > Compressed (zipped) folder
Important: Make sure to zip the contents of the web-build folder, not the web-build folder itself.
Common Issues and Solutions
Missing Assets
If your game assets aren't loading:
- Ensure you're using the correct asset loading approach for Expo
- Use the
require()
or import syntax for static assets - Place assets in the
assets
folder and reference them correctly
Platform-Specific Code
If your game has platform-specific code:
- Use Platform.OS to conditionally run code for specific platforms
- Create platform-specific files (e.g., Component.web.js)
- Use the
expo-device
package for more detailed platform information
Web Performance
To improve your game's web performance:
- Minimize the use of heavy animations that might perform poorly on web
- Optimize asset sizes (compress images, use appropriate formats)
- Consider using web-specific optimizations in your platform-specific code
- Use React.memo and useCallback to prevent unnecessary re-renders
Implementing Game Session Tracking
All games uploaded to our platform must implement session tracking functionality. This allows us to track player sessions and maintain leaderboards for your game.
Important: Your game bundle must include API calls to our session tracking endpoints. Our validation system will check for these calls during the upload process. Games without proper session tracking implementation will be rejected.
Option 1: Use Our SDK (Recommended)
For Expo web games, you can use our JavaScript SDK by adding it to your project:
Step 1: Add the SDK to your project
Create a public
folder in your project root (if it doesn't exist) and copy our SDK file into it:
mkdir -p public cp /path/to/game-session-tracker.js public/
You can download the SDK here or view an example implementation.
Then, in your app.json
, add the following to ensure the SDK is included in your web build:
{ "expo": { "web": { "publicPath": "/", "build": { "babel": { "include": ["public"] } } } } }
Step 2: Load the SDK in your app
In your main component or game screen, add the following:
import React, { useEffect, useState } from 'react'; import { View, Text, Button, Platform } from 'react-native'; export default function GameScreen() { const [tracker, setTracker] = useState(null); const [sessionData, setSessionData] = useState(null); useEffect(() => { // Only run on web platform if (Platform.OS === 'web') { // Load the SDK script const script = document.createElement('script'); script.src = '/game-session-tracker.js'; script.async = true; script.onload = () => initializeTracker(); document.body.appendChild(script); // Parse URL parameters const urlParams = new URLSearchParams(window.location.search); setSessionData({ token: urlParams.get('token') || generateRandomToken(), appVersion: urlParams.get('appVersion') || '1.0.0', language: urlParams.get('language') || 'en', sessionStartTime: new Date() }); } return () => { // Cleanup script on unmount if (Platform.OS === 'web') { const script = document.querySelector('script[src="/game-session-tracker.js"]'); if (script) document.body.removeChild(script); } }; }, []); const initializeTracker = () => { if (window.GameSessionTracker) { // The SDK will automatically detect the subdomain setTracker(new window.GameSessionTracker()); } }; const generateRandomToken = () => { return 'player-' + Math.random().toString(36).substring(2, 15); }; // Function to call when game ends const handleGameOver = (score) => { if (tracker) { tracker.endSession(score) .then(response => { console.log("Session recorded successfully", response); }) .catch(error => { console.error("Failed to record session", error); }); } }; // Function to display leaderboard const showLeaderboard = () => { if (tracker) { tracker.getLeaderboard(10) .then(response => { const leaderboard = response.data.leaderboard; // Display leaderboard in your UI console.log("Leaderboard:", leaderboard); }) .catch(error => { console.error("Failed to fetch leaderboard", error); }); } }; return ( <View style="{ flex: 1, justifyContent: 'center', alignItems: 'center' }"> {/* Your game UI */} <Button title="End Game" onPress={() => handleGameOver(1500)} /> <Button title="Show Leaderboard" onPress={showLeaderboard} /> </View> ); }
Option 2: Custom Implementation
If you prefer to implement session tracking yourself, follow these steps:
1. Create a session tracking service:
// services/SessionTracker.js export default class SessionTracker { constructor() { this.token = null; this.appVersion = null; this.language = null; this.sessionStartTime = null; this.initialized = false; // Initialize on creation this.initialize(); } initialize() { if (typeof window === 'undefined') return; // Skip on server-side const urlParams = new URLSearchParams(window.location.search); this.token = urlParams.get('token') || this.generateRandomToken(); this.appVersion = urlParams.get('appVersion') || '1.0.0'; this.language = urlParams.get('language') || 'en'; this.sessionStartTime = new Date(); this.initialized = true; } generateRandomToken() { return 'player-' + Math.random().toString(36).substring(2, 15); } async endSession(score) { if (!this.initialized) { console.error('Session tracker not initialized'); return; } const sessionEndTime = new Date(); const payload = { token: this.token, session_start_time: this.sessionStartTime.toISOString(), session_end_time: sessionEndTime.toISOString(), score: score, app_version: this.appVersion, language: this.language }; // Get the subdomain from the current hostname const hostname = window.location.hostname; const parts = hostname.split('.'); const subdomain = parts.length > 2 ? parts[0] : ''; // REQUIRED: This exact endpoint must be called when a game session ends try { const response = await fetch(`/api/v1/games/${subdomain}/session-ended`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); return await response.json(); } catch (error) { console.error('Failed to record session', error); throw error; } } async getLeaderboard(limit = 10) { // Get the subdomain from the current hostname const hostname = window.location.hostname; const parts = hostname.split('.'); const subdomain = parts.length > 2 ? parts[0] : ''; try { const response = await fetch(`/api/v1/games/${subdomain}/leaderboard?limit=${limit}`); return await response.json(); } catch (error) { console.error('Failed to fetch leaderboard', error); throw error; } } }
2. Use the service in your game component:
// screens/GameScreen.js import React, { useEffect, useState } from 'react'; import { View, Button, Platform } from 'react-native'; import SessionTracker from '../services/SessionTracker'; export default function GameScreen() { const [tracker, setTracker] = useState(null); useEffect(() => { // Initialize the tracker on web platform only if (Platform.OS === 'web') { // The tracker will automatically detect the subdomain setTracker(new SessionTracker()); } }, []); const handleGameOver = async (score) => { if (tracker) { try { const result = await tracker.endSession(score); console.log('Session recorded successfully', result); } catch (error) { console.error('Error recording session', error); } } }; return ( <View style="{ flex: 1, justifyContent: 'center', alignItems: 'center' }"> {/* Your game UI */} <Button title="End Game" onPress={() => handleGameOver(1500)} /> </View> ); }
Testing Your Implementation
Before uploading, test your session tracking implementation:
- Run your Expo web app with
yarn web
orexpo start --web
- Add URL parameters to your local development URL (e.g.,
http://localhost:19006/?token=test123&appVersion=1.0.0&language=en
) - Use browser developer tools to verify network requests are being made correctly
In browser developer tools, check that the POST request to /api/v1/games/:gameId/session-ended
is being made with all required parameters.
Uploading to Jiran Games
Log in to Your Account
Sign in to your Jiran Games developer account.
Create a New Game
Click on "Add New Game" and fill in the required information:
- Game Name
- Description
- Game Logo (recommended size: 512x512px)
- Tags (to help users find your game)
- Subdomain (this will be your game's URL: yourgame.jiran.games)
Upload Your Game Bundle
Select the ZIP file containing your Expo web build and upload it.
Note: Our system will automatically detect your Expo web build structure and serve it correctly. We specifically look for the _expo
folder, assets
folder, and required HTML files.
Submit for Review
After uploading, your game will be marked as "pending_review". Our team will review it to ensure it meets our guidelines.
The review process typically takes 1-2 business days.
Alternative: Using Expo's dist Folder
Some newer versions of Expo may output to a dist
folder instead of web-build
. Our platform supports this structure as well.
Build with Newer Expo Versions
If you're using a newer version of Expo, the build command might create a dist
folder instead:
# Build for web with newer Expo versions npx expo export:web
This will create a dist
folder with the following structure:
dist/ ├── _expo/ ├── assets/ ├── index.html ├── not-found.html ├── _sitemap.html └── ...
Zip the dist Folder
Compress your dist directory into a ZIP file:
# On macOS/Linux cd dist zip -r ../my-game.zip ./* # On Windows # Right-click the dist folder > Send to > Compressed (zipped) folder
Note: Our platform will automatically detect both the web-build
and dist
folder structures for Expo projects.