The chat-client repo is private: true and exposes nothing on npm. The "server component" pattern below is not a feature of a published package — it's a one-file convenience used in the yugo reference repo (/Users/imzee/projects/yugo/src/components/AppmintChatServer.tsx) that reads env vars on the server and forwards them to a 'use client' wrapper. Copy it into your project; it is ~8 lines of code.
The widget itself runs only in the browser (it injects a <script> tag and calls window.AppmintChat.init). The "server component" wrapper exists purely to pull configuration out of process env on the server so secret-ish values (org id, config id) never appear in your client bundle.
When to use this pattern
Reach for the server wrapper when:
- You're on the App Router and want to read
process.envserver-side. - You'd rather not duplicate
NEXT_PUBLIC_*prefixes for org/config ids.
For everything else (Vite, Pages Router, CSR-only React apps) the client wrapper is fine on its own.
The two files
Two files: a 'use client' wrapper that does the script-tag injection (same as the React installation), and a thin server component that reads env vars and forwards them.
AppmintChat.tsx (client component)
This is the wrapper from the React installation page. Copy it verbatim into src/components/AppmintChat.tsx.
AppmintChatServer.tsx (server component)
import AppmintChat from './AppmintChat';
export default function AppmintChatServer() {
const configId = process.env.chatConfigId;
const appId = process.env.chatAppId;
const orgId = process.env.NEXT_PUBLIC_ORG_ID;
return <AppmintChat configId={configId} appId={appId} orgId={orgId} />;
}
That's the entire server component — no fetch, no preloading, no SSR rendering of the panel. It just reads env vars and renders the client wrapper. The browser still does the actual script-tag injection on mount.
Usage in a layout
Drop the server component into your root layout so the widget is available on every page.
// app/layout.tsx
import AppmintChatServer from '@/components/AppmintChatServer';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<AppmintChatServer />
</body>
</html>
);
}
No 'use client' directive on the layout — the server wrapper is a true server component, and the inner AppmintChat carries its own 'use client' boundary.
Environment variables
Set these in .env.local (and your hosting provider's env config):
NEXT_PUBLIC_ORG_ID=your-org-id
chatConfigId=67169ccce05fcd6eb50e6dff
chatAppId=storefront
The yugo reference uses lowercase, non-NEXT_PUBLIC_ names for chatConfigId and chatAppId so they're only readable on the server. Pick any names you like — the only one that needs to be public is NEXT_PUBLIC_ORG_ID if you also want it accessible from other client components.
Authenticated visitors
The yugo wrapper does not pre-fetch a chat token, mint a session, or call AppEngine — it only forwards env vars. If you need to attach an authenticated visitor's identity, extend AppmintChat.tsx to accept user and token props, then forward them from AppmintChatServer.tsx:
import AppmintChat from './AppmintChat';
import { getCurrentVisitor, getChatToken } from '@/lib/auth';
export default async function AppmintChatServer() {
const visitor = await getCurrentVisitor();
const chatToken = visitor ? await getChatToken(visitor.email) : null;
return (
<AppmintChat
orgId={process.env.NEXT_PUBLIC_ORG_ID}
configId={process.env.chatConfigId}
appId={process.env.chatAppId}
user={visitor ?? undefined}
token={chatToken ?? undefined}
/>
);
}
getCurrentVisitor and getChatToken are your code — typically wrappers around AppEngine's /profile/customer/signin and a token-mint helper. There is no built-in restHelper.register(...) exposed by the chat bundle.
Streaming and Suspense
AppmintChatServer returns synchronously after reading env vars. There's nothing to suspend on. If your layout streams, no special handling is needed — the wrapper renders an empty <div> and then the client takes over once the script loads.
Verifying behaviour
If the bubble doesn't appear:
- Open DevTools → Network and look for
https://web.appmint.space/chat-client/appmint-chat.jsreturning 200. - In the console, check
typeof window.AppmintChat === 'object'after the script loads. - Confirm
configIdandappIdare present (the wrapper returnsnullif either is missing).
The ground truth lives in chat-client/src/index.tsx (the script-tag entry) and yugo/src/components/AppmintChat.tsx (the wrapper this page mirrors).