Elements vs Next.js

Markdown

Next.js is an open-source web app framework for React, made by Vercel. You write pages as React components, which are React Server Components by default. A file that starts with "use client" marks a boundary: it and the modules it imports become Client Components, which are prerendered on the server and then also run in the browser, for state, event handlers and browser APIs (server and client components). Next.js adds routing based on folders and file names, server-side rendering and static generation, route handlers for APIs, and Server Actions for calling server code from forms and event handlers. It ships its own bundler, Turbopack, which by default runs the next dev development server and the next build production build. Next.js apps run on Node.js, and they deploy to Vercel's hosting platform or to a server you run yourself.

Elements is an integrated app environment for building web apps, built for people and their agents. At its center is a project server that runs while you work, and around it the same system includes a package installer, a test runner, a bundled Postgres database, and a deploy command for any Ubuntu server you reach over SSH. It ships with a default framework that just works: routing, server rendering, typed server functions, sessions, realtime data, background jobs and email, with pages written in Elements HTML, standard HTML with reactive expressions. Elements apps are written in TypeScript and run on Node.js.

The difference is design, and who pays for it. Next.js is an open-source framework that Vercel, a venture-backed hosting company, develops, funds and gives away, and Vercel sells hosting (governance, Vercel pricing). On top of web standards that have existed for decades, HTML, HTTP caching headers, cookies, forms and URLs, Next.js has built its own layer: special file names and folder patterns for routing, directives that decide where code runs and what gets cached, an image component, a font loader, and two caching models chosen by a config flag. Past the plumbing every app needs, each one is another thing to learn and debug. Elements gives you the primitives a web app needs, working out of the box: sessions, HTTP caching, HTML templates and routing. A route is one line of code, a page is HTML, an image is an <img> tag, a server call is a typed function call, and the session is already there. The same system also brings the database, migrations, tests and deploy that a Next.js app adds from other libraries.

At a Glance

Elements Next.js
What it is A project server, a default framework, Postgres, tests and deploy, shipped as one coherent system A React framework with a bundler and dev server
Business model Sells the system you build with, per machine, and you deploy to commodity servers at your provider's price (pricing) A venture-subsidized open-source framework, developed at Vercel, which sells hosting for a fee per seat plus metered usage (governance, Vercel pricing)
Templates Elements HTML: standard HTML with typed reactive expressions React components in JSX, split into server and client components
Routing One app.route() line per URL, using the web standard URLPattern Folders, with nine special file names and ten folder-name patterns
Request data req.params and session, plain values params, searchParams, cookies() and headers() are Promises you await
Directives None; a @rpc comment marks a server function "use client", "use server", "use cache", and two variants of "use cache"
Server functions @rpc: typed calls for reads and writes, checked at compile time Server Actions: for mutations, sent as POST requests, one at a time
Sign-in and sessions Built in, the same over HTTP and WebSockets Build it yourself, or pick one of twelve libraries the docs list
Caching Automatic, on the HTTP standard: every page gets an ETag and unchanged pages answer 304, every asset has a hash in its file name and is cached by the browser and any CDN in between, and signed-in pages stay private (server rendering, assets) "use cache" with cacheLife profiles, or segment config options with that model switched off; Next.js writes the header
Images Standard <img> and <picture> tags; files content-hashed and cached long term next/image, resized and converted at request time; metered on Vercel
Database Postgres, bundled, with sql() built in Bring your own database and client
Migrations Applied when you save them Not included
Tests Part of the build Not included; the docs cover four outside tools
Realtime LiveTable and channels Not included
Background jobs and cron Built in Not included
Email Built in; templates use the page syntax Not included
Build errors Every one in one report: tests, migrations, program analysis and type checking, found in milliseconds as you work Type errors at next build; tests and migrations run in outside tools
Deploy elements deploy to any Ubuntu server over SSH, often under a second Vercel, or self-host with next start or Docker

Abstractions Over Decades-Old Standards

The web already has a standard for most of what an app does. HTML has <img>, <a> and <form>. HTTP has Cache-Control. A cookie is a header, and a URL is a pattern. Next.js puts an abstraction of its own over each one, well past the plumbing an app needs, and each of those is one more API to learn and one more set of rules to debug:

  • Images go through next/image, a component that "extends the HTML <img> element" (images). A section below shows what it adds.
  • Fonts come from next/font: you call a font as a function and apply the result through className (fonts), in place of a CSS @font-face rule.
  • Caching is a "use cache" directive and a cacheLife profile, and Next.js writes the Cache-Control header from them (CDN caching).
  • Cookies are read with await cookies(). Headers, URL parameters and query strings are Promises too: headers(), params and searchParams can only be read asynchronously (async request APIs).
  • Code before a request goes in proxy.ts, a file convention with rules of its own, covered below (proxy.js).
  • URLs are folders. The project structure page lists nine routing files (page, layout, template, loading, error, global-error, not-found, default and route) and ten folder-name patterns, from [slug] and [[...slug]] to route groups in parentheses, @folder slots for parallel routes and (..)(..) for intercepting routes.

Above the folders sit three directives, "use client", "use server" and "use cache", plus two variants of the last. Caching has the most moving parts. A config flag picks one of two models: Cache Components with the flag on, and route segment options such as dynamic and revalidate with it off. With the flag on, a page that exports dynamic or revalidate is an error (Cache Components). Rendering a random id per request then takes an await connection() call and a <Suspense> wrapper (caching).

Setup grows the same way. The next.config reference lists 72 options (next.config.js). The current caching model stays off until you set cacheComponents: true. An image from another host has to be listed in remotePatterns, and an image quality other than 75 is served as 75 unless it is listed in qualities (Image). The proxy file has rules of its own: one per project, its matcher values must be constants, and without a matcher it "runs on every request, including static files," so the docs warn that auth logic or redirects "can unintentionally block CSS, JS, or images from loading." It runs on the Node.js runtime with no option to change it, and it "should not be used as a full session management or authorization solution" (proxy.js, proxy).

Elements has fewer words to learn because it uses the ones you already know. A route is a URL pattern. A page is HTML. An image is an <img> tag. A server function is a TypeScript function marked @rpc. Caching is HTTP headers. The sections below show each one.

Agents Need Correction, Not Endless Abstraction

For an agent writing an app, the value past the basic plumbing is not in more abstractions or a bigger ecosystem. It is in correction: finding out what it got wrong the moment it gets it wrong. That does not discount frameworks. Agents do not write machine code to build a web app, and between machine code at one end and a package for subtraction at the other, a framework pays off at the plumbing every app needs. Elements ships that plumbing: routing with app.route(), typed server calls with @rpc, sessions with session.login(), and sql() with the database driver beneath it.

Past the plumbing, the returns diminish. Each further abstraction, like Next.js's directives, its two caching models, and its image and font components, costs an agent more to learn and get right than it saves. The same holds for the React ecosystem that much of the case for Next.js rests on. A library helps where it does something the agent cannot write quickly from standards, and that surface is small: an agent would rather write HTML and CSS than learn a React component library, and it does not need a dependency for a few lines of TypeScript.

Elements keeps abstractions minimal past the plumbing, with pages in HTML and CSS and server code as TypeScript function calls, and puts its engineering into correction. The framework and its build were made together, so one coherent system checks every page, function, query, test and migration. Every build error, from tests, migrations, program analysis and type checking, is found in milliseconds as you work, and elements build -json returns it in microseconds.

Sessions Work Out of the Box

Almost every app needs to know who is signed in. Next.js has no session of its own. Its authentication guide recommends a library and lists twelve, plus two for sessions. Built by hand, the guide's version is a JWT library to sign the cookie, cookie flags you set yourself, and a verifySession helper you call next to every data read, because layouts "don't re-render on navigation, meaning the user session won't be checked on every route change."

In Elements, the session is built in, with nothing to install, configure or call on every read. Signing in is one call:

app/shared/services/auth.tsElements
import { sql, session } from "@elements/app"; /** @rpc */ export function signin(email: string, password: string) { let user = sql<{ id: string }>(`select id from users where email = ${email} and passwordHash = crypt(${password}, passwordHash)`).firstOrThrow("invalid email or password"); session.login({ userId: user.id, userName: email }); }

In Elements, reading the session is one call too, the same in a route, an @rpc function or a template:

app/pages/dashboard/template.ehtmlElements
import { session } from "@elements/app"; <html> <header> <span e:if={session.isLoggedIn()}>Signed in as {session.get("userName")}</span> <a e:else href="/signin">Sign in</a> </header> </html>

There is no library to choose, no cookie flags, no JWT code and no helper to call on every read. session.getOrThrow("userId") returns the signed-in user's id or answers 401, so the check sits in the function that reads the data. Each request knows who is signed in, a session stays alive while the user is active, and it works the same over WebSockets as over plain requests. A template that reads the session redraws the moment the user signs in or out.

Caching Works Out of the Box, on the HTTP Standard

HTTP already has a caching standard: the server sends Cache-Control, and browsers and CDNs follow it. In Next.js, a page component has no response object to put a header on; a page's header goes in a path-matched headers() list in next.config (headers) or in the proxy file. With Cache Components switched on, the docs cache a page by starting the component with a "use cache" directive and a cacheLife() call that names a profile, and Next.js writes the header from the profile (caching, CDN caching). 'minutes' is not a number of minutes. It names three timers: five minutes in the browser, a background refresh after one minute, and expiry after an hour (cacheLife). Behind a CDN, responses also vary on up to five custom request headers, and the docs note that "Many CDNs don't support Vary without additional configuration," so Next.js adds an _rsc parameter to the URL as a cache key, which the CDN has to keep (CDN caching).

In Elements, you set the header in the route and return the page:

app/pages/pricing/index.tsElements
import { Request, Response, sql } from "@elements/app"; import html, { Plan } from "./template"; export default function route(req: Request, res: Response) { res.setHeader("Cache-Control", "public, max-age=300"); return new html({ plans: sql<Plan>(`select name, price from plans`).all() }); }

That line is only for pages you want a CDN to hold. Everything else is cached automatically. Every HTML page carries an ETag computed from the page's source, its data and the session, so a browser revisiting an unchanged page gets a 304 Not Modified with no body (server rendering). Every script, stylesheet, image and font the build emits has a hash in its file name and is cached by default, by the browser and by any CDN in between, and a deploy that changes a file changes its name, so no browser or CDN serves an old copy of a changed file (assets, build). When the visitor is signed in, the response is kept out of shared caches, so one user's page is never served to another. It does the right thing by default: no configuration required, nothing abstract to learn, and nothing to invalidate.

An Image Is an img Tag

In Next.js, images go through next/image, which the docs say provides size optimization, protection from layout shift, lazy loading, and resizing on demand (images):

app/page.tsxNext.js
import Image from 'next/image' import profile from './profile.png' export default function Page() { return <Image src={profile} alt="Picture of the author" sizes="(max-width: 800px) 100vw, 800px" /> }

Most of that is HTML the browser already has. loading="lazy" is native lazy loading, and the component's reference says it "uses browser native lazy loading" (Image). width and height on an <img> reserve its space before the file arrives. srcset and sizes let the browser pick a size, <picture> with a WebP <source> lets it pick a format, and decoding="async" is one more attribute. The component writes those attributes for you.

The real work is making the smaller files and the WebP or AVIF copies, and Next.js does it per request. Each srcset entry points at /_next/image?url=...&w=640&q=75, an image optimizer that resizes and converts the file on a cache miss. The docs list "Resizing images on-demand" among its features (images), and the self-hosting guide warns that on glibc-based Linux the optimizer "may require additional configuration to prevent excessive memory usage" (self-hosting). It brings settings of its own: remotePatterns, a qualities allowlist that defaults to 75 alone, formats, deviceSizes, imageSizes and minimumCacheTTL, plus a disk cache of optimized images about which the docs say "There is no mechanism to invalidate the cache at this time" (Image). On Vercel it is a metered line on the bill. Past what a plan includes, transformations, cache reads and cache writes are each billed per unit, at rates that vary by region, and a transformation is billed on "every cache MISS and STALE" (image optimization pricing, Vercel pricing). On Hobby, which Vercel restricts to non-commercial personal use, once the monthly transformation allowance is used, new images "fail to optimize and instead return a runtime error response with 402 status code" (image optimization pricing). Vercel publishes a guide titled "How to reduce Vercel Image Optimization costs."

In Elements, you write the standard tags. A relative path in src becomes a content-hashed URL, an import gives you the hashed URL as a string for srcset, and hashed files are cached long term (assets):

app/pages/profile/template.ehtmlElements
import small from "./profile-800.webp"; import large from "./profile-1600.webp"; <html> <picture> <source type="image/webp" srcset={`${small} 800w, ${large} 1600w`} sizes="(max-width: 800px) 100vw, 800px"> <img src="./profile-800.jpg" alt="Picture of the author" width="800" height="800" loading="lazy" decoding="async"> </picture> </html>

The smaller copies are made once, by a step you choose: an export from your design tool, a script in your build, or a resize when a file is uploaded. There is no image server running on every request and no per-image bill.

Templates Are HTML, With No Server and Client Split

A template turns data into markup. In Next.js, a page is JSX: a list is a .map() with a key on each item, a condition is a && expression, and HTML attributes are renamed, class to className and for to htmlFor. A page is a Server Component by default. To add a click handler and keep the page a Server Component, that part moves into a separate file that begins with "use client", and every prop it receives has to be serializable, and "Ordinary functions, such as event handlers, cannot cross" (use client, directives).

In Elements, a page is a file of Elements HTML: real HTML tags with TypeScript in braces, and e:for and e:if for lists and conditions.

app/pages/tasks/template.ehtmlElements
<ul> <li e:for={task of tasks} class={{ done: task.done }}>{task.title}</li> </ul> <p e:if={tasks.length === 0}>Nothing to do.</p>

The same file holds the page's click handlers, with no second file and no directive. It renders to HTML on the server, picks up its handlers in the browser, and from then on a change to tasks touches only the markup that reads it. Form inputs bind both ways by default. There are no hooks to learn and nothing to memoize. React's own answer to re-render tuning, the React Compiler, is not enabled by default in Next.js, and the release post that made it stable says to "expect compile times in development and during builds to be higher" with it on (Next.js 16).

Routes Are Lines of Code, Not Folders

Routing decides which code answers which URL. In Next.js, the folder tree is the router: a blog post page lives at app/blog/[slug]/page.tsx, and a JSON endpoint beside it has to go in a separate folder, such as app/api/posts/route.ts, because a route.ts cannot sit in the same folder as a page.tsx (route handlers). The page receives the URL parameter as params: Promise<{ slug: string }> and has to await it (page.js).

In Elements, every route is one line in index.ts, written as a web standard URLPattern:

index.tsElements
app.route("/", home); app.route("/blog/:slug", post); app.route("/api/posts", listPosts);

In Elements, the handler reads the parameter as a plain string:

app/pages/post/index.tsElements
import { Request, Response, sql } from "@elements/app"; import html, { Post } from "./template"; export default function route(req: Request, res: Response) { let post = sql<Post>(`select * from posts where slug = ${req.params.slug}`).firstOrThrow("post not found"); return new html({ post }); }

A handler returns a page, an object or a string, so one router serves HTML and JSON. Patterns take named segments, wildcards, optional groups and inline regex. Every route is readable in one place, and renaming a folder changes no URL.

A Form Calls a Typed Function

Saving a form is the most common write in a web app. In Next.js, it takes a Server Action in a "use server" file, a client component in a "use client" file that shows errors with useActionState, and a call that tells Next.js what to re-render (mutating data). The values arrive as FormData entries to check by hand, not typed fields. revalidatePath is one of four ways to make the page show the write, beside updateTag, revalidateTag and refresh, and they differ in whether the response waits for fresh data (Server Actions). Server Actions are always POST requests, and the client "dispatches and awaits them one at a time," which the docs call an implementation detail that may change (mutating data).

In Elements, the list, the form and the server function are one file:

app/pages/tasks/template.ehtmlElements
import { sql, session, ValidationError } from "@elements/app"; export interface Task { id: string; title: string; done: boolean; } interface TaskForm { title: string; error: string; } /** @rpc */ export function addTask(title: string): Task { session.isLoggedInOrThrow(); if (!title.trim()) { throw new ValidationError("a task needs a title"); } return sql<Task>(`insert into tasks (title) values (${title}) returning *`).firstOrThrow(); } function onSubmit(tasks: Task[], form: TaskForm) { try { tasks.push(addTask(form.title)); form.title = ""; form.error = ""; } catch (err: any) { form.error = err.message; } } <html (tasks: Task[], private form: TaskForm = { title: "", error: "" })> <ul> <li e:for={task of tasks} class={{ done: task.done }}>{task.title}</li> </ul> <p e:if={tasks.length === 0}>Nothing to do.</p> <form onsubmit={() => onSubmit(tasks, form)}> <input value={form.title} required> <p e:if={form.error} role="alert">{form.error}</p> <button type="submit">Add</button> </form> </html>

The input is bound to a typed object, so form.title is a string. addTask is called like any function, and its body never reaches the browser. It returns the new row, and pushing that onto tasks updates the list. Nothing needs revalidating, because nothing was cached. Browser code that touches sql, tx or session.login directly does not compile, and the error reads: "Security error: cannot call server code from the browser without going through an rpc function."

One Feature, Both Ways: Sign Up

A signup form shows the difference end to end. Following the Next.js authentication guide, it takes a client component using useActionState, a Server Action, a schema library, a password hashing package, a database client, and the session code the guide has you write.

In Elements, it is one server function, one template and one migration. The server function creates the user and signs them in:

app/shared/services/auth.tsElements
import { sql, session, ValidationError } from "@elements/app"; /** @rpc */ export function signup(email: string, password: string) { if (password.length < 8) { throw new ValidationError("password must be at least 8 characters"); } let user = sql<{ id: string }>(` insert into users (email, passwordHash) values (${email}, crypt(${password}, genSalt('bf', 12))) returning id `).firstOrThrow(); session.login({ userId: user.id, userName: email }); }

In Elements, the template calls it like a local function:

app/pages/signup/template.ehtmlElements
import { redirect } from "@elements/app"; import { signup } from "#app/shared/services/auth"; interface SignupForm { email: string; password: string; error: string; } function onSubmit(form: SignupForm) { try { signup(form.email, form.password); redirect("/"); } catch (err: any) { form.error = err.message; } } <html (private form: SignupForm = { email: "", password: "", error: "" })> <form onsubmit={() => onSubmit(form)}> <p e:if={form.error} role="alert">{form.error}</p> <input type="email" value={form.email} required> <input type="password" value={form.password} required> <button type="submit">Sign up</button> </form> </html>

In Elements, the table is a SQL migration:

app/migrations/20261001082723-add-users.migration.sqlElements
create table users ( id uuid primary key default uuidGenerateV7(), email text not null unique, passwordHash text not null, createdAt timestamptz not null default now() );

The password is hashed with bcrypt inside Postgres, with nothing to install. The call from the form to the server is typed. Migrations are SQL files, applied as soon as you save one, and the unique email column keeps two accounts from sharing an address.

Nothing Else to Assemble

A Next.js app still needs a database, migrations, tests, realtime, background jobs and a deploy pipeline, each chosen and connected separately. Elements includes them.

SQL. Postgres is bundled and configured on your machine and your servers. SQL goes in the sql() function, and a value you put in a query can never turn into SQL injection. If you rewrite a migration in development, Elements synchronizes the change to the database.

Realtime. Bind a page to a LiveTable, and an edit shows up for everyone viewing it as a single updated row. Anything that is not a row goes out on a channel. Both run over Postgres, with no extra service. Next.js has no WebSocket API of its own: Vercel's docs say it "does not expose an API for handling WebSocket upgrades" and point to a workaround (Vercel WebSockets).

Tests. Next.js has no test runner; its testing guide covers setting up Cypress, Jest, Playwright or Vitest. Elements runs tests inside the build, each in a Postgres transaction that is discarded afterward, and a failing test fails the build.

Deploy. elements deploy goes over SSH to an Ubuntu server from any provider, sends just the files that changed, and often finishes in under a second. Each machine runs the tests, the lead one runs migrations once, and if anything fails the deploy halts with the current release still serving.

Slow Builds, on Next.js's Own Record

Next.js publishes a guide called "How to optimize your local development environment." It opens: "As your application grows, you might notice slower compilation times during local development." Its advice includes adding the project to your antivirus exclusions, turning off macOS Gatekeeper checks for your terminal, not developing inside Docker on a Mac or Windows machine, and importing icons one file at a time (local development). The guide grew out of a GitHub issue, "[NEXT-1143] Dev mode slow compilation", filed in April 2023, which gathered 598 comments and 246 reactions before Vercel closed and locked it in April 2025, pointing readers to the new guide. A second guide covers memory: "As applications grow and become more feature rich, they can demand more resources when developing locally or creating production builds" (memory usage).

Reports are still open today, including "NextJS build always going out of heap memory when building", filed in March 2025 with 32 reactions, and "Dev server is going crazy on memory usage", a dev server reaching 7 GB on Next.js 16.1.6, with 17 reactions. When the dev server's memory runs high, Next.js restarts it in the middle of your session. Elements vs Turbopack covers those restarts and the memory reports behind them.

Elements' project server runs while you work and holds the build graph and build state in memory. On every save, the build state is updated almost instantly. It builds in milliseconds and answers agents and humans in microseconds, and elements build -json returns every build error together: test results, migration failures, program analysis, and type checking of code and templates.

Free Framework, Metered Hosting

Next.js is a venture-subsidized open-source framework, and the primary on-ramp to Vercel's metered hosting business. Vercel is a venture-backed company (Vercel). Next.js's governance page says its research and development "is led by the core team working full-time at Vercel" (governance). What Vercel sells is its platform: a fee per developer seat, plus metered compute, function invocations, build minutes and image optimization, with CDN traffic billed per unit or by capacity tier (Vercel pricing). The framework costs nothing, and on Vercel it deploys with no setup. Running it on more than one server of your own takes a custom cache handler and a shared encryption key (self-hosting), and on other hosts it runs through an adapter (OpenNext). When the art app Cara grew from 40,000 to 650,000 users in a week in 2024, TechCrunch reported a Vercel bill of $96,280 for that week (TechCrunch). Elements vs Vercel covers that bill.

That is one of two ways an open-source framework or library usually gets made. The other, and the more common, is volunteers, paid by donations or not at all. In Tidelift's 2024 survey of more than 400 maintainers, 60% described themselves as unpaid hobbyists, just 12% earned most of their income from maintaining projects, and 60% had quit or considered quitting (Tidelift 2024 report). A Linux Foundation and Harvard study of 49 widely used open-source projects found that in 23% of them, one developer wrote more than 80% of the code added in 2021 (Census II). That is a risky foundation to bank a business on.

Next.js is the other model: funded, as the on-ramp to hosting. Elements is a company that sells its tooling directly, per machine, with nothing metered (pricing), so what it earns from is the tooling itself. Elements charges for the tooling, not the framework: the app framework and runtime packages such as @elements/app are MIT licensed, and everything you build is yours (licenses).

Moving From Next.js

An agent can port a Next.js app easily, because its pages, routes and server code map onto the Elements app framework's HTML templates, routes and @rpc functions. You keep TypeScript and your npm packages, and you gain sessions, a database, tests and deploy with no assembly.

Questions

What is the difference between Elements and Next.js?

Next.js is an open-source React framework built around folder-based routing, server and client components, directives such as "use client" and "use cache", and two framework caching models chosen by a config flag. Elements is an integrated app environment whose default framework gives you sessions, HTTP caching, HTML templates and routing that work out of the box: a route is a line of code, a page is HTML, a server call is a typed function, and caching is plain HTTP headers. The same system bundles Postgres with migrations, runs tests and deploys to your servers.

Is Elements a Next.js alternative?

Yes. It takes the place of Next.js plus the auth library, database client, migration tool, test runner and deploy pipeline a Next.js app collects.

Does Elements cache pages like Next.js?

Yes, automatically, on the HTTP standard. Every HTML page carries an ETag computed from its source, data and session, so an unchanged page answers 304 Not Modified. Every built script, stylesheet, image and font has a hash in its file name and is cached by default, by the browser and by any CDN in between, and a deploy that changes a file changes its name. Pages for a signed-in visitor stay out of shared caches. To let a CDN hold a page, a route sets Cache-Control in one line. There is no cache directive to learn and nothing to invalidate.

Does Elements have an image component like next/image?

No. In Elements you write the standard img and picture tags, with loading, width, height, srcset and sizes, which the browser already supports. Image files are content-hashed and cached long term, and any resizing or format conversion is a build or upload step you choose, so there is no image server running on every request and no per-image bill. On Vercel, next/image optimization is metered per transformation and per cache read and write (Vercel pricing).

Who makes Next.js?

Vercel, a venture-backed hosting company. Next.js's research and development is led by a core team working full-time at Vercel (governance), and Vercel sells hosting (Vercel pricing). Elements is sold directly, per machine, so its revenue comes from the tooling itself (pricing).

Can I use React components in Elements?

No. Elements has its own html language: standard HTML with typed reactive expressions. Pages render on the server and update only what changed in the browser, with nothing to tune. React has more ready-made components, but an agent builds a component from HTML and CSS faster than it can find, install and adapt one, with no hooks or re-render tuning to learn.

Can I use npm packages in Elements?

Yes. They install as is into a standard node_modules folder, and the build handles the install step itself.

Does Elements do server-side rendering?

Yes, every page, automatically. Each page arrives as complete HTML, so search engines index its content and visitors see it on the first paint. The same page is also fully reactive and interactive in the browser. You get both with no compromise, and never decide which parts render on the server.

Can I deploy a Next.js app with Elements?

Not as a Next.js app. A Next.js app has to be built by Next.js's own build command, next build (Next.js Turbopack docs). The Elements build tooling works with TypeScript and JavaScript code in general, not with one framework, and an agent can port an existing Next.js app to the Elements app framework easily.