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, 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 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
UI utilities"use web"@ui/lib/*.ts

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')

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

  1. Add directives to environment-specific files - Be explicit
  2. Never add directives to shared files - Use runtime constants 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