Keeping FiveM NUI fast

10 min read5 sections · 4 questions answered
Short answer

Send NUI messages only when values change, and throttle continuous data (speed, fuel) to a few updates per second. Hide closed UIs with display: none, animate only transform and opacity, avoid large blurs and shadows, keep images the size they are displayed at, and stop React from re-rendering the whole tree on every message. Measure the Lua side in resmon and the page in the devtools Performance tab.

A HUD that costs 2 FPS is a HUD every player pays for, all session long. NUI cost has two halves: the Lua side sending data (visible in resmon) and the browser side rendering it (visible as lower FPS and in the devtools). Both are easy to keep small once you know the patterns.

Send less, less often

client.lua — speedometer updates only on change
local last = {}

CreateThread(function()
    while true do
        local sleep = 500
        local veh = GetVehiclePedIsIn(PlayerPedId(), false)
        if veh ~= 0 then
            sleep = 150
            local speed = math.floor(GetEntitySpeed(veh) * 3.6)
            local fuel = math.floor(GetVehicleFuelLevel(veh))
            if speed ~= last.speed or fuel ~= last.fuel then
                last.speed, last.fuel = speed, fuel
                SendNUIMessage({ action = 'vehicle', speed = speed, fuel = fuel })
            end
        elseif last.speed then
            last = {}
            SendNUIMessage({ action = 'vehicle', hide = true })
        end
        Wait(sleep)
    end
end)

Each message is encoded to JSON and handed to the browser process. Rounding values and sending only changes turns hundreds of messages per second into a handful.

Cheap CSS

ExpensiveCheaper
Animating width, top, lefttransform: translate/scale
backdrop-filter, big filter: blur()Semi-transparent solid backgrounds
Many large, soft box-shadowsOne small shadow or a border
Hidden with opacity: 0display: none when closed
Huge PNGs scaled down in CSSImages exported at display size, WebP

React and framework UIs

  • Store fast-changing values (speed, health) in small components, not in a top-level state that re-renders everything.
  • Use React.memo / selectors so a speed update does not re-render the inventory grid.
  • Unmount closed screens instead of rendering them invisibly.
  • Build for production — development builds are much slower.

Measuring

Resmon shows the Lua cost of your client thread (aim for 0.00–0.02 ms idle). Rendering cost does not appear there: record a few seconds in the devtools Performance tab and compare in-game FPS with the UI open and closed. See NUI devtools and resmon explained.

Loading screens

Loading screens compete with the game loading itself. The same rules apply, plus file size — see loading screen performance.

Common questions

Why does my HUD lower FPS?

Usually messages sent every frame, heavy CSS effects, or a framework re-rendering everything on each update.

How often should a HUD update?

Only when values change, and at most around 4–10 times per second for continuous values like speed.

Does resmon show NUI rendering cost?

No. Resmon shows the Lua side; use in-game FPS and the devtools Performance tab for rendering.

Should I hide UI with opacity or display none?

display: none for closed UIs — invisible elements still cost rendering.

Want a loading screen you never have to debug?

Build it in the browser, export once, and edit it whenever — no HTML, no re-uploads.

Start free

More guides