Right now your portal redirects every visitor straight into the auth provider's login screen because there's no public route. That's a rough first impression — visitors land on a screen that says "sign in" without any explanation of what they're signing into. A small structural change fixes that:
- Public landing page at
/— explains what the portal is, with a "Sign in" CTA. - Everything else protected — docs, API references, etc. are gated behind
protectedRoutes.
This way, unauthenticated visitors get a real homepage, and clicking "Sign in" (or any protected link) sends them through your auth provider with a return URL back to the page they wanted. We're also shipping a related fix soon (PR #2398) that removes an extra "login or register?" dialog and goes straight to the auth provider — together with the homepage, this gives a much cleaner flow.
Create src/Landingpage.tsx. The cosmo-cargo example has a complete reference you can copy from: examples/cosmo-cargo/src/Landingpage.tsx.
The key trick is using useAuth() so the CTA changes once a user is signed in:
import { Button, Head, Link, Typography } from "zudoku/components";
import { useAuth } from "zudoku/hooks";
export const Landingpage = () => {
const { isAuthenticated, signIn } = useAuth();
return (
<section>
<Head><title>Home</title></Head>
<h1>Welcome to the Acme Developer Portal</h1>
<Typography>
<p>Browse our APIs, manage keys, and ship integrations fast.</p>
</Typography>
{isAuthenticated ? (
<Button asChild size="xl">
<Link to="/documentation">Go to docs</Link>
</Button>
) : (
<Button size="xl" onClick={() => signIn()}>
Sign in to continue
</Button>
)}
</section>
);
};In zudoku.config.tsx, add a custom-page entry at the top of navigation with path: "/". See examples/cosmo-cargo/zudoku.config.tsx lines 258–263 for the exact pattern:
import { Landingpage } from "./src/Landingpage";
const config: ZudokuConfig = {
// ...
navigation: [
{
type: "custom-page",
path: "/",
element: <Landingpage />,
},
// ...the rest of your navigation
],
};Docs reference: Custom Pages guide and type: custom-page in navigation.
Use protectedRoutes to lock the rest of the site. The simple array form is enough for most cases:
const config: ZudokuConfig = {
// ...
protectedRoutes: [
"/documentation/*",
"/api/*",
"/catalog/*",
// add any other paths you want gated
],
};See the full Protected Routes docs for advanced patterns.
For pages that should only appear in the sidebar/header for signed-in users, add display: "auth" to the navigation item. Cosmo-cargo uses this for its "Members" and "VIP Lounge" pages — see zudoku.config.tsx lines 334–347:
{
type: "custom-page",
path: "/only-members",
label: "Only members",
display: "auth", // hidden until signed in
element: <MembersOnly />,
},You can use the same display: "auth" on category, doc, and link items too.
If you want a working reference to clone locally and look around in:
git clone https://github.com/zuplo/zudoku.git
cd zudoku
pnpm install
nx run cosmo-cargo:devThe files most relevant to your setup:
examples/cosmo-cargo/src/Landingpage.tsx— public homepage componentexamples/cosmo-cargo/src/MembersOnly.tsx— example of an auth-only page usinguseAuth()examples/cosmo-cargo/zudoku.config.tsx— full config showingcustom-pageat/,protectedRoutes,display: "auth", andauthenticationtogether
You also hit an unintended behavior where Zudoku showed an intermediate "log in or register?" dialog before bouncing to your auth provider. We're removing that dialog in PR #2398 so unauthorized navigation auto-redirects to the provider directly — you'll get this for free on your next Zudoku upgrade. The homepage approach above is the right setup either way and will only get cleaner once that ships.
Let me know once you've stood up the landing page — happy to look over the config and call out anything I'd tweak.