Configuration

Project Structure

Below is a breakdown of the ERR Boilerplate project structure and configuration options.

Directory Structure

err_boilerplate/
├── client/           # Client-side Lua scripts
│   ├── main.lua      # Main client entry point
│   └── utils.lua     # Utility functions for client
├── config/           # Configuration files
│   └── shared.lua    # Shared configuration between client and server
├── web/              # Frontend React application
│   ├── public/       # Static assets
│   ├── src/          # Source code
│   │   ├── app/      # Main application components
│   │   ├── assets/   # Images, fonts, etc.
│   │   ├── components/ # UI components
│   │   │   ├── ui/   # Shadcn UI components
│   │   │   └── LibIcon.tsx # FontAwesome icon wrapper
│   │   ├── hooks/    # Custom React hooks
│   │   ├── lib/      # Utility libraries
│   │   ├── providers/ # React context providers
│   │   ├── transitions/ # Animation components
│   │   ├── utils/    # Utility functions
│   │   ├── index.css # Global CSS
│   │   └── main.tsx  # React entry point
│   ├── index.html    # HTML template
│   ├── package.json  # Dependencies and scripts
│   ├── tailwind.config.js # TailwindCSS configuration
│   ├── tsconfig.json # TypeScript configuration
│   └── vite.config.ts # Vite bundler configuration
└── fxmanifest.lua    # FiveM resource manifest

FXManifest Configuration

The fxmanifest.lua file defines how your resource is loaded by the FiveM server:

fx_version 'cerulean'
game 'gta5'
lua54 'yes'
 
author "Err Boilerplate"
description "A modern FiveM boilerplate with latest React, Tailwind, and other modern technologies"
version '1.0.0'
 
server_scripts {
  'server/**/*'
}
client_scripts {
  'client/**/*',
}
shared_scripts {
  '@ox_lib/init.lua',
}
 
-- ui_page 'http://localhost:3000/' -- (for local dev)
ui_page 'web/build/index.html'
 
files {
  'web/build/index.html',
  'web/build/**/*',
  'config/*.lua'
}

Client-Side Configuration

The client-side Lua scripts handle the communication between the game and the UI:

Main Client File (main.lua)

local config = require 'config.shared'
 
local function toggleNuiFrame(shouldShow)
    SetNuiFocus(shouldShow, shouldShow)
end
  
RegisterCommand('show-nui', function()
    toggleNuiFrame(true)
    SendReactMessage('showUi', true)
    debugPrint('Show NUI frame')
end)
  
RegisterNUICallback('hideFrame', function(_, cb)
    toggleNuiFrame(false)
    debugPrint('Hide NUI frame')
    cb({})
end)
  
RegisterNUICallback('getClientData', function(data, cb)
    local curCoords = GetEntityCoords(PlayerPedId())
    local retData <const> = { x = curCoords.x, y = curCoords.y, z = curCoords.z }
    cb(retData)
end)

Utility Functions (utils.lua)

function SendReactMessage(action, data)
    SendNUIMessage({
        action = action,
        data = data
    })
end
 
local currentResourceName = GetCurrentResourceName()
local debugIsEnabled = GetConvarInt(('%s-debugMode'):format(currentResourceName), 0) == 1
 
function debugPrint(...)
    if not debugIsEnabled then return end
    local args <const> = { ... }
  
    local appendStr = ''
    for _, v in ipairs(args) do
      appendStr = appendStr .. ' ' .. tostring(v)
    end
    local msgTemplate = '^3[%s]^0%s'
    local finalMsg = msgTemplate:format(currentResourceName, appendStr)
    print(finalMsg)
end

Frontend Configuration

Package.json

The package.json file defines the dependencies and scripts for the React application:

{
  "name": "web",
  "version": "1.0.0",
  "scripts": {
    "start": "vite",
    "start:game": "vite build --watch",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@fortawesome/fontawesome-svg-core": "^6.7.2",
    "@fortawesome/free-brands-svg-icons": "^6.7.2",
    "@fortawesome/free-regular-svg-icons": "^6.7.2",
    "@fortawesome/free-solid-svg-icons": "^6.7.2",
    "@fortawesome/react-fontawesome": "^0.2.2",
    "@radix-ui/react-scroll-area": "^1.2.4",
    "@radix-ui/react-separator": "^1.1.3",
    "@radix-ui/react-slot": "^1.2.0",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "framer-motion": "^12.5.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^3.0.2",
    "tailwindcss-animate": "^1.0.7"
  }
}

TailwindCSS Configuration

The tailwind.config.js file configures the TailwindCSS framework:

module.exports = {
  darkMode: ["class"],
  content: [
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {
      borderRadius: {
        lg: 'var(--radius)',
        md: 'calc(var(--radius) - 2px)',
        sm: 'calc(var(--radius) - 4px)'
      },
      colors: {
        background: 'hsl(var(--background))',
        foreground: 'hsl(var(--foreground))',
        card: {
          DEFAULT: 'hsl(var(--card))',
          foreground: 'hsl(var(--card-foreground))'
        },
        // Additional color configurations...
      }
    }
  }
}

Shadcn UI Configuration

The components.json file configures the Shadcn UI components:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/index.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}

NUI Communication

Client to UI Communication

To send data from the client to the UI, use the SendReactMessage function:

SendReactMessage('actionName', { key = 'value' })

UI to Client Communication

To send data from the UI to the client, use the fetchNui function:

import { fetchNui } from '@/utils/fetchNui';
 
// Example usage
const handleButtonClick = async () => {
  const response = await fetchNui('getClientData', { someData: 'value' });
  console.log(response); // { x: 100, y: 100, z: 100 }
};

Listening for NUI Events

To listen for events from the client in your React components, use the useNuiEvent hook:

import { useNuiEvent } from '@/hooks/useNuiEvent';
 
const MyComponent = () => {
  useNuiEvent('showUi', (data) => {
    // Handle the event data
    console.log(data);
  });
  
  return <div>My Component</div>;
};

Debugging

Browser Development

For development in a browser environment, the boilerplate includes utilities to simulate NUI events:

import { debugData } from '@/utils/debugData';
 
// Simulate a 'showUi' event from the client
debugData([
  {
    action: 'showUi',
    data: true,
  },
]);

In-Game Debugging

To enable debug mode in-game, set the following ConVar in your server.cfg:

set err_boilerplate-debugMode 1

This will enable debug messages from the debugPrint function in the client scripts.

Example Usage

Creating a Custom Component

import React from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import LibIcon from '@/components/LibIcon';
import { fetchNui } from '@/utils/fetchNui';
 
const MyCustomComponent = () => {
  const [data, setData] = React.useState(null);
  
  const handleFetchData = async () => {
    const response = await fetchNui('getClientData');
    setData(response);
  };
  
  return (
    <Card>
      <CardHeader>
        <CardTitle>My Custom Component</CardTitle>
      </CardHeader>
      <CardContent>
        <Button onClick={handleFetchData}>
          <LibIcon icon="location-dot" />
          Get Player Location
        </Button>
        
        {data && (
          <div className="mt-4">
            <p>X: {data.x}</p>
            <p>Y: {data.y}</p>
            <p>Z: {data.z}</p>
          </div>
        )}
      </CardContent>
    </Card>
  );
};
 
export default MyCustomComponent;