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:

app/components/Counter.tsxtypescript
"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:

app/pages/index.tsxtypescript
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

1

Bundles the file and serves it at /__client-component/<id>.js

2

During SSR, emits a <span data-hydrate-id="…"> placeholder with serialized props

3

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:

app/components/Dialog.tsxtypescript
"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>
            )}
        </>
    )
}
app/pages/index.tsx (server)typescript
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>
    )
}
Keep client components leanThe smaller a client component, the smaller its bundle. Move data fetching and static markup into server components and pass results down as props.