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;