Next.js Upload Guide

Learn how to prepare and upload your Next.js games

Back to guides

Preparing Your Next.js Game

Next.js is a powerful React framework that can be used to build interactive web games. Our platform supports both static exports and server-side rendered Next.js applications.

Set Up Your Next.js Project

If you haven't already, create a new Next.js project:

# Using yarn (recommended) yarn create next-app my-game # Using npm npx create-next-app my-game # Navigate to your project cd my-game

Note: We recommend using yarn as your package manager for Next.js projects.

Configure Your Project

Update your next.config.js file based on your deployment strategy:

For Static Export (Recommended)

/** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', images: { unoptimized: true, }, // If your game will be hosted in a subdirectory // basePath: '', // trailingSlash: true, } module.exports = nextConfig

For Server-Side Rendering

/** @type {import('next').NextConfig} */ const nextConfig = { // Any custom configuration // Note: Server-side rendering requires additional server setup } module.exports = nextConfig

Important: Static export is recommended for most games as it's simpler to deploy and has better performance for client-side interactive applications.

Build Your Game

Run the build command to create a production-ready version of your game:

For Static Export

# Using yarn yarn build # Using npm npm run build

This will create an out folder containing your static export with the following structure:

out/ ├── index.html ├── _next/ │ ├── static/ │ │ ├── chunks/ │ │ ├── css/ │ │ └── ... │ └── ... └── ...

For Server-Side Rendering

# Using yarn yarn build # Using npm npm run build

This will create a .next folder containing your server-side application with the following structure:

.next/ ├── server/ │ ├── pages/ │ ├── chunks/ │ └── ... ├── static/ │ ├── chunks/ │ ├── css/ │ └── ... └── ...

Test Your Build Locally

Before uploading, test your built game to ensure everything works correctly:

For Static Export

# Using a simple HTTP server npx serve out # Or with Next.js yarn start

For Server-Side Rendering

# Start the Next.js server yarn start

Check that all game features, assets, and interactions work as expected.

Zip Your Build

Compress your build folder into a ZIP file:

For Static Export

# On macOS/Linux cd out zip -r ../my-game.zip ./* # On Windows # Right-click the out folder > Send to > Compressed (zipped) folder

For Server-Side Rendering

# On macOS/Linux zip -r my-game.zip .next package.json yarn.lock public # On Windows # Create a zip file containing .next, package.json, yarn.lock, and public folder

Important: For static exports, make sure to zip the contents of the out folder, not the out folder itself.

Common Issues and Solutions

Image Optimization

If you're using Next.js Image component and having issues:

API Routes in Static Exports

API routes don't work in static exports. Instead:

Routing Issues

If you're experiencing routing problems:

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)

The easiest way to implement session tracking is to use our JavaScript SDK. You can download the SDK here or view an example implementation.

In your Next.js project, you can add it in a few different ways:

Method A: Using Script Component

// In your _app.js or a game component
import Script from 'next/script';
import { useEffect, useState } from 'react';

export default function Game() {
  const [tracker, setTracker] = useState(null);
  
  useEffect(() => {
    // Initialize tracker when the SDK is loaded
    if (window.GameSessionTracker) {
      setTracker(new window.GameSessionTracker());
    }
  }, []);
  
  return (
    <div>
      <Script
        src="/sdk/game-session-tracker.js"
        strategy="afterInteractive"
        onLoad="() => {
          setTracker(new window.GameSessionTracker());
        }"
      />
      <!-- Your game UI -->
    </div>
  );
}

Method B: Using Custom Document

// In pages/_document.js
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
        <script src="/sdk/game-session-tracker.js"></script>
      </body>
    </Html>
  );
}

Then use the tracker in your game component:

import { useEffect, useState } from 'react';

export default function GameComponent() {
  const [tracker, setTracker] = useState(null);
  
  useEffect(() => {
    // Wait for the SDK to load
    const initTracker = () => {
      if (window.GameSessionTracker) {
        // The SDK will automatically detect the subdomain
        setTracker(new window.GameSessionTracker());
      } else {
        // If not loaded yet, try again in a moment
        setTimeout(initTracker, 100);
      }
    };
    
    initTracker();
  }, []);
  
  // 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
        })
        .catch(error => {
          console.error("Failed to fetch leaderboard", error);
        });
    }
  };
  
  return (
    <div>
      <!-- Your game UI -->
      <button onClick="() => handleGameOver(1500)">End Game</button>
      <button onClick="showLeaderboard">Show Leaderboard</button>
    </div>
  );
}

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:

// components/Game.js
import { useEffect, useState } from 'react';
import SessionTracker from '../services/sessionTracker';

export default function Game() {
  const [tracker, setTracker] = useState(null);
  
  useEffect(() => {
    // Initialize the tracker on client-side only
    if (typeof window !== 'undefined') {
      // 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 (
    <div>
      <!-- Your game UI -->
      <button onClick="() => handleGameOver(1500)">End Game</button>
    </div>
  );
}

Testing Your Implementation

Before uploading, test your session tracking implementation:

  1. Run your Next.js app locally with yarn dev
  2. Add URL parameters to your local development URL (e.g., http://localhost:3000/?token=test123&appVersion=1.0.0&language=en)
  3. 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/:subdomain/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 Next.js build and upload it.

Note: Our system will automatically detect your Next.js build structure (static or server) and serve it correctly.

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.