Pages and embeds
Compose the persistent Activity shell and file-based screens.
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.
1import { EmbedOutlet, LudicordActivity } from "ludicord";2import Navbar from "../components/navbar";3import "./globals.css";45export 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.
1import { useDiscordUser } from "ludicord/discord";23export 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.
1app/embeds/home/embed.tsx2app/embeds/profile/[userId]/embed.tsx3app/embeds/game/[...room]/embed.tsx
1import { useEmbedParams } from "ludicord/navigation";23export 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.
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.