Skip to content
🌊 You are viewing documentation for the Maelstrom pre-release channel. Pin a stable release for production.

Project Structure

A well-organized project keeps server, client, and shared code cleanly separated so Riptide can load them in the right order. Here is the recommended layout for a small-to-medium game.


my-game/ ← your repository root
├── luau_packages/ ← managed by pesde (don't edit manually)
│ └── Riptide/
├── src/
│ ├── shared/ ← code shared by both server and client
│ │ ├── CurrencyConfig.lua
│ │ └── ItemDatabase.lua
│ │
│ ├── server/
│ │ ├── main.server.lua ← SERVER entry point
│ │ └── Services/ ← server-only modules
│ │ ├── DataService.lua
│ │ ├── Economy/
│ │ │ └── CoinsService.lua
│ │ └── MatchService.lua
│ │
│ └── client/
│ ├── main.client.lua ← CLIENT entry point
│ └── Controllers/ ← client-only modules
│ ├── UIController.lua
│ └── InputController.lua
└── default.project.json ← Rojo project file

Inside Roblox Studio (after Rojo sync), this maps to:

ReplicatedStorage/
├── Packages/ ← luau_packages (Riptide lives here)
└── SharedModules/ ← src/shared
ServerScriptService/
├── main ← src/server/main.server.lua (Script)
└── Services/ ← src/server/Services/ (Folder)
StarterPlayer/StarterPlayerScripts/
├── main ← src/client/main.client.lua (LocalScript)
└── Controllers/ ← src/client/Controllers/ (Folder)

FolderWhere it lives in StudioPassed to Launch asPurpose
Services/ServerScriptServiceModulesFolder (server)Server-only logic — data, economy, combat
Controllers/StarterPlayerScriptsModulesFolder (client)Client-only logic — UI, input, camera
SharedModules/ReplicatedStorageSharedModulesFolder (both)Code used by both sides — configs, utilities
Components/ReplicatedStorage (optional)ComponentsFolder (both)CollectionService component classes
Packages/ReplicatedStorageExternal dependencies (require() directly)

--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local Riptide = require(ReplicatedStorage.Packages.Riptide).Server
Riptide.Launch({
ModulesFolder = ServerScriptService.Services,
SharedModulesFolder = ReplicatedStorage.SharedModules, -- optional
ComponentsFolder = ReplicatedStorage.Components, -- optional
})
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Riptide = require(ReplicatedStorage.Packages.Riptide).Client
Riptide.Launch({
ModulesFolder = Players.LocalPlayer.PlayerScripts.Controllers,
SharedModulesFolder = ReplicatedStorage.SharedModules, -- optional
ComponentsFolder = ReplicatedStorage.Components, -- optional
})

Both ModulesFolder and SharedModulesFolder accept an array of folders, making it easy to split large codebases across multiple directories:

Riptide.Launch({
ModulesFolder = {
ServerScriptService.CoreServices,
ServerScriptService.GameplayServices,
ServerScriptService.AdminServices,
},
SharedModulesFolder = {
ReplicatedStorage.SharedModules,
ReplicatedStorage.LibModules,
},
})

Modules from each folder are discovered in array order, then deduplicated.


If you use ComponentService for CollectionService-based components, create a folder (e.g. Components/) in ReplicatedStorage and pass it via ComponentsFolder:

ReplicatedStorage/
└── Components/
├── Lava.lua ← kills players on touch
└── SpeedPad.lua ← boosts player walk speed
Riptide.Launch({
ModulesFolder = ServerScriptService.Services,
ComponentsFolder = ReplicatedStorage.Components,
})

See the Component Service reference for details.


  • Module Lifecycle — understand how Riptide loads and starts your modules.
  • Network — communicate between server and client.