Pages & Routing

NukeJS maps your file system directly to URL routes — no router config, no imports required.

File → URL mapping

Every .tsx file inside app/pages/ that exports a default component becomes a route.

FileURL
app/pages/index.tsx/
app/pages/about.tsx/about
app/pages/blog/index.tsx/blog
app/pages/blog/[slug].tsx/blog/:slug
app/pages/docs/[...path].tsx/docs/* (catch-all)
app/pages/files/[[...path]].tsx/files or /files/* (optional catch-all)

Basic page

Pages can be async — async code runs on the server before the HTML is sent:

app/pages/index.tsxtypescript
export default async function Home() {
    const posts = await db.getPosts() // runs on server, no JS sent to browser

    return (
        <main>
            <h1>Blog</h1>
            {posts.map(p => (
                <article key={p.id}>
                    <h2>{p.title}</h2>
                    <p>{p.excerpt}</p>
                </article>
            ))}
        </main>
    )
}

Dynamic routes

Wrap a segment in [brackets]. The segment name becomes a prop on the component:

app/pages/blog/[slug].tsxtypescript
export default async function BlogPost({ slug }: { slug: string }) {
    const post = await fetchPost(slug)

    if (!post) {
        // handle 404 however you like
        return <h1>Post not found</h1>
    }

    return (
        <article>
            <h1>{post.title}</h1>
            <p>{post.content}</p>
        </article>
    )
}

Catch-all routes

Use [...param] to match one or more path segments. The value is a string[]:

app/pages/docs/[...path].tsxtypescript
export default function Docs({ path }: { path: string[] }) {
    // /docs/getting-started/installation → path = ['getting-started', 'installation']
    return <DocViewer segments={path} />
}

Use [[...param]] (double brackets) to also match the route with no trailing segments.

Route specificity

When multiple files could match, the most specific one wins:

/users/profile  →  pages/users/profile.tsx    (static — wins)
/users/42       →  pages/users/[id].tsx       (dynamic)
/users/a/b/c    →  pages/users/[...rest].tsx  (catch-all)
ℹ️
layout.tsx is never a routeFiles named layout.tsx are always treated as layout wrappers, never as routes. Visiting /blog/layout will not match app/pages/blog/layout.tsx.