Skip to main content

Environment Directives

Environment directives control which runtime environments include specific code. They enable clean separation between server, client, and web code.

Available Directives

DirectiveRuntimeDescription
"use server"ServerFiveM server runtime (Node.js)
"use client"ClientFiveM client runtime (game natives)
"use web"WebReact UI runtime (browser)

Usage

Directives must be the first non-empty, non-comment line of the file:

"use server";

import { getMongoDb } from "@core/db/client";

export async function findUser(id: string) {
const db = await getMongoDb();
return db.collection("users").findOne({ id });
}

Build Behavior

Each directive tells the build system how to handle the file:

DirectiveClient BuildServer BuildWeb Build
"use client"Keep codeGenerate stubsGenerate stubs
"use server"Generate stubsKeep codeGenerate stubs
"use web"Generate stubsGenerate stubsKeep code

Stubs export undefined for all exports, allowing imports to resolve without including actual code.

When to Use Each Directive

"use server"

Database access, server events, player state:

"use server";

import { getMongoDb, withMongoTransaction } from "@core/db/client";

export async function createCharacter(data: CharacterData) {
const db = await getMongoDb();
return db.collection("characters").insertOne(data);
}

"use client"

FiveM natives, entity classes, HUD messaging:

"use client";

import { sendHudEvent } from "@core/hud";

export function showNotification(message: string) {
sendHudEvent("notification:show", { message });
}

"use web"

React components, Redux slices, UI features:

"use web";

import { useAppSelector } from "@ui/hooks";

export function StatusPanel() {
const health = useAppSelector((state) => state.status.health);
return <div>Health: {health}</div>;
}

Files That Need Directives

File TypeDirectiveExample
Repository files"use server"repository.ts
Database utilities"use server"@core/db/client.ts
Server event wrappers"use server"@core/events/server.ts
Entity classes"use client"@core/classes/Entity.ts
Client event wrappers"use client"@core/events/client.ts
HUD messaging"use client"@core/hud.ts
React components"use web"Panel.tsx, Page.tsx
Redux slices"use web"state/slice.ts
UI feature definitions"use web"feature.tsx

Files That Must NOT Have Directives

Critical

The following files must NEVER have environment directives:

File TypeReason
module.tsImported by all runtimes for feature discovery
types.tsType-only files are runtime-agnostic
config.tsUsually shared configuration
Service classes (*.service.ts)Use decorators + __RUNTIME__ checks
Shared utilitiesRuntime-agnostic code

Why module.ts Must Never Have Directives

Module files are imported by:

  1. src/runtime/client/main.ts - for client bootstrap
  2. src/runtime/server/main.ts - for server bootstrap
  3. ui/src/modules/index.ts - for UI feature discovery

If a module.ts has "use server", the web build will stub it to export default undefined, breaking the UI's feature registry and causing:

Cannot read properties of undefined (reading 'pages')

Using __RUNTIME__ Checks

For runtime-specific code in shared files, use the global __RUNTIME__ variable:

// module.ts - NO DIRECTIVE
import { registerModule } from "@core/module";
import { fivemEvents } from "@core/events/server"; // Will be undefined in non-server

export default registerModule({
name: "my-module",

onStart() {
if (__RUNTIME__ === "server") {
// fivemEvents is available here
fivemEvents.playerJoining.on((id) => {
console.log(`Player ${id} joining`);
});
}

if (__RUNTIME__ === "client") {
// Client-only code
}

if (__RUNTIME__ === "web") {
// Web-only code (rare in modules)
}
},
});

Importing Across Environments

You can safely import from files with directives - they'll be stubbed in incompatible builds:

// This works in server build, returns undefined in client/web
import { getMongoDb } from "@core/db/client"; // "use server" file

if (__RUNTIME__ === "server") {
const db = await getMongoDb(); // Safe - we're on server
}

Best Practices

  1. Add directives to environment-specific files - Be explicit
  2. Never add directives to shared files - Use __RUNTIME__ instead
  3. Keep module.ts directive-free - Required for feature discovery
  4. Use path aliases - Consistent imports regardless of runtime
  5. Check runtime before using stubbed imports - Avoid undefined errors