Environment Directives
Environment directives control which runtime environments include specific code. They enable clean separation between server, client, and web code.
Available Directives
| Directive | Runtime | Description |
|---|---|---|
"use server" | Server | FiveM server runtime (Node.js) |
"use client" | Client | FiveM client runtime (game natives) |
"use web" | Web | React 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:
| Directive | Client Build | Server Build | Web Build |
|---|---|---|---|
"use client" | Keep code | Generate stubs | Generate stubs |
"use server" | Generate stubs | Keep code | Generate stubs |
"use web" | Generate stubs | Generate stubs | Keep 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, observables, UI features:
"use web";
import { useService, useObservable } from "@core/web/react";
import { NeedsService } from "@modules/core/needs/needs.service";
export function StatusPanel() {
const needsService = useService(NeedsService);
const state = useObservable(needsService.hudState);
return <div>Health: {state.health}</div>;
}Files That Need Directives
| File Type | Directive | Example |
|---|---|---|
| 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 |
| UI utilities | "use web" | @ui/lib/*.ts |
Files That Must NOT Have Directives
The following files must NEVER have environment directives:
| File Type | Reason |
|---|---|
module.ts | Imported by all runtimes for feature discovery |
types.ts | Type-only files are runtime-agnostic |
config.ts | Usually shared configuration |
Service classes (*.service.ts) | Use decorators + __RUNTIME__ checks |
| Shared utilities | Runtime-agnostic code |
Why module.ts Must Never Have Directives
Module files are imported by:
src/runtime/client/main.ts- for client bootstrapsrc/runtime/server/main.ts- for server bootstrapui/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')Runtime Checks
For runtime-specific code in shared files, use the constants from @core/runtime:
import { IS_CLIENT, IS_SERVER, IS_WEB } from "@core/runtime";
// These are compile-time constants that enable dead-code elimination:
// - IS_CLIENT: true only in client builds
// - IS_SERVER: true only in server builds
// - IS_WEB: true only in web builds
if (IS_SERVER) {
// This entire block is stripped from client/web builds
}The raw __RUNTIME__ global is also available (replaced at build time with "client", "server", or "web"), but prefer the IS_* constants for cleaner code.
Example Module with Runtime Checks
// module.ts - NO DIRECTIVE
import { registerModule } from "@core/module";
import { fivemEvents } from "@core/events/server"; // Will be undefined in non-server
import { IS_CLIENT, IS_SERVER } from "@core/runtime";
export default registerModule({
name: "my-module",
onStart() {
if (IS_SERVER) {
// fivemEvents is available here
fivemEvents.playerJoining.on((id) => {
console.log(`Player ${id} joining`);
});
}
if (IS_CLIENT) {
// Client-only code
}
},
});Importing Across Environments
You can safely import from files with directives - they'll be stubbed in incompatible builds:
import { IS_SERVER } from "@core/runtime";
// This works in server build, returns undefined in client/web
import { getMongoDb } from "@core/db/client"; // "use server" file
if (IS_SERVER) {
const db = await getMongoDb(); // Safe - we're on server
}Best Practices
- Add directives to environment-specific files - Be explicit
- Never add directives to shared files - Use runtime constants instead
- Keep module.ts directive-free - Required for feature discovery
- Use path aliases - Consistent imports regardless of runtime
- Check runtime before using stubbed imports - Avoid undefined errors