Ludicord
Guide 05 · Build the UI

Pages and embeds

Compose the persistent Activity shell and file-based screens.

Ludicord 3.1.0Public source 20c4889

Ludicord separates the persistent Activity shell from the screen currently shown inside it. This keeps providers, navigation, authentication, and shared connections alive while embed routes change.

#Build the Activity root

app/pages.tsx is the only required root component. Wrap EmbedOutlet with providers and UI that should survive every navigation.

app/pages.tsxtsx
1import { EmbedOutlet, LudicordActivity } from "ludicord";
2import Navbar from "../components/navbar";
3import "./globals.css";
4
5export default function Pages() {
6 return (
7 <LudicordActivity defaultEmbed="home">
8 <Navbar />
9 <EmbedOutlet />
10 </LudicordActivity>
11 );
12}

LudicordActivity initializes the client runtime and error/loading boundaries. EmbedOutlet renders the route selected by the embed router.

#Create an embed

Every screen default-exports a component from a file named embed.tsx.

app/embeds/home/embed.tsxtsx
1import { useDiscordUser } from "ludicord/discord";
2
3export default function embed() {
4 const user = useDiscordUser();
5 return <main>Welcome {user?.displayName ?? "player"}</main>;
6}

The component name stays lowercase by convention, but it follows normal React component rules: call hooks at the top level, return JSX, and move reusable UI into components.

#Add nested and dynamic screens

Folders create route segments. Dynamic folders expose typed params.

texttext
1app/embeds/home/embed.tsx
2app/embeds/profile/[userId]/embed.tsx
3app/embeds/game/[...room]/embed.tsx
app/embeds/profile/[userId]/embed.tsxtsx
1import { useEmbedParams } from "ludicord/navigation";
2
3export default function embed() {
4 const { userId } = useEmbedParams("profile/:userId");
5 return <main>Profile {userId}</main>;
6}

Catch-all params represent the remaining route path. Validate any value that later crosses into trusted server logic.

#Use layouts and boundaries

The generated project relies on framework fallbacks for optional conventions. Larger sections can add a nested layout.tsx, loading.tsx, or error.tsx near the embed routes they protect.

ConventionResponsibility
layout.tsxShared UI around child embeds in that segment
loading.tsxPending UI while an embed module loads
error.tsxRecoverable client error state with a reset action
not-found.tsxUI for an embed path that does not exist
minimize.tsxCompact global Activity presentation managed by Discord

Keep the generated tree small until a real section needs its own boundary.

#Decide what belongs in the shell

Put a value in pages.tsx when it must survive navigation: theme providers, top-level navigation, query providers, shared WebSocket context, or an audio controller. Put it in an embed when it only belongs to that screen.

Avoid storing screen-specific state in the root without a reason. Long-lived state increases coupling and can make navigation preserve data the user expects to reset.

#Next step

Use Navigation to connect the screens with typed links, history, params, and prefetching.