Client Components
Add "use client" to any component to opt it into the browser bundle. Everything else is server-only by default.
Server components by default
Every component in NukeJS is a server component by default. It runs once on the server, its output becomes HTML, and zero JavaScript is sent to the browser for it. You can use async/await, import server-only packages, read environment variables — it all stays on the server.
Opting in with "use client"
Add "use client" as the very first line to make a component interactive:
"use client"
import { useState } from 'react'
export default function Counter({ initial = 0 }: { initial?: number }) {
const [count, setCount] = useState(initial)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>+</button>
<button onClick={() => setCount(c => c - 1)}>−</button>
</div>
)
}Use it from a server component — NukeJS handles bundling and hydration automatically:
import Counter from '../components/Counter'
export default async function Home() {
const initialCount = await db.getCount() // server-only
return (
<main>
<h1>Counter demo</h1>
<Counter initial={initialCount} /> {/* hydrated in browser */}
</main>
)
}What NukeJS does under the hood
Bundles the file and serves it at /__client-component/<id>.js
During SSR, emits a <span data-hydrate-id="…"> placeholder with serialized props
In the browser, the NukeJS runtime fetches the bundle and mounts the component with React
Rules
"use client"must be the first non-comment line of the file- The component must have a named default export (NukeJS uses the function name during hydration)
- Props must be JSON-serializable — no functions, no class instances, no Dates
- React elements (children, slots) are supported — NukeJS serializes and reconstructs them
Passing children from server to client
You can pass JSX from a server component into a client component. NukeJS serializes the tree and reconstructs it in the browser before mounting:
"use client"
import { useState } from 'react'
export default function Dialog({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false)
return (
<>
<button onClick={() => setOpen(true)}>Open</button>
{open && (
<dialog open>
<div>{children}</div> {/* server-rendered content, reconstructed */}
<button onClick={() => setOpen(false)}>Close</button>
</dialog>
)}
</>
)
}import Dialog from '../components/Dialog'
export default async function Home() {
const terms = await fetchTermsOfService() // server fetch
return (
<Dialog>
<h2>Terms of Service</h2>
<p>{terms.text}</p> {/* server data inside a client component */}
</Dialog>
)
}