Skip to content

EreoJS vs Next.js ​

This comparison helps developers understand the differences between EreoJS and Next.js, making it easier to choose the right framework or migrate between them.

Overview ​

AspectEreoJSNext.js
RuntimeBunNode.js
BundlerBunWebpack/Turbopack
PhilosophyExplicit, simpleFeature-rich, conventions
Client Components'use client' + islands (data-hydrate)'use client' directive
Data FetchingLoaders/ActionsServer Components, API routes
CachingTag-based, explicitISR, fetch cache, implicit

Routing ​

File Structure ​

Both use file-based routing with similar conventions:

Next.js (App Router):

app/
├── page.tsx           # /
├── about/page.tsx     # /about
├── posts/
│   ├── page.tsx       # /posts
│   └── [id]/page.tsx  # /posts/:id
└── (marketing)/
    └── pricing/page.tsx  # /pricing

EreoJS:

routes/
├── index.tsx          # /
├── about.tsx          # /about
├── posts/
│   ├── index.tsx      # /posts
│   └── [id].tsx       # /posts/:id
└── (marketing)/
    └── pricing.tsx    # /pricing

Key differences:

  • Next.js requires page.tsx in directories
  • EreoJS uses index.tsx or direct file names
  • Both support route groups with parentheses

Layouts ​

Next.js:

tsx
// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

EreoJS:

tsx
// routes/_layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}

Nearly identical, but EreoJS uses _layout.tsx prefix.

Data Fetching ​

Server-Side Data ​

Next.js (App Router - Server Components):

tsx
// app/posts/[id]/page.tsx
async function getPost(id: string) {
  const res = await fetch(`https://api.example.com/posts/${id}`)
  return res.json()
}

export default async function Post({ params }) {
  const post = await getPost(params.id)

  return <h1>{post.title}</h1>
}

EreoJS:

tsx
// routes/posts/[id].tsx
import { createLoader } from '@ereo/data'

export const loader = createLoader(async ({ params }) => {
  const res = await fetch(`https://api.example.com/posts/${params.id}`)
  const post = await res.json()
  return { post }
})

export default function Post({ loaderData }) {
  return <h1>{loaderData.post.title}</h1>
}

Key differences:

  • Next.js uses async Server Components
  • EreoJS uses explicit loader functions
  • EreoJS separates data fetching from rendering

Mutations ​

Next.js (Server Actions):

tsx
// app/posts/new/page.tsx
async function createPost(formData: FormData) {
  'use server'
  await db.posts.create({
    title: formData.get('title')
  })
  redirect('/posts')
}

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  )
}

EreoJS:

tsx
// routes/posts/new.tsx
import { createAction, redirect } from '@ereo/data'
import { Form } from '@ereo/client'

export const action = createAction(async ({ request }) => {
  const formData = await request.formData()
  await db.posts.create({
    title: formData.get('title')
  })
  return redirect('/posts')
})

export default function NewPost() {
  return (
    <Form method="post">
      <input name="title" />
      <button type="submit">Create</button>
    </Form>
  )
}

Key differences:

  • Next.js uses 'use server' directive
  • EreoJS uses explicit action exports
  • Both support progressive enhancement

Client-Side Interactivity ​

Next.js:

tsx
'use client'

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

EreoJS (Islands):

EreoJS also supports 'use client' — the familiar pattern from Next.js:

tsx
// app/components/Counter.tsx
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

// In a route — just import and use:
<Counter />

For advanced hydration control, EreoJS additionally offers data-island attributes:

tsx
<Counter data-island="Counter" data-hydrate="idle" />

Key differences:

  • Both support 'use client', so the migration path is familiar
  • EreoJS adds data-island for hydration strategies (idle, visible, media, never)
  • EreoJS ships less JavaScript by default — only island components get hydrated

Caching ​

Next.js:

tsx
// Implicit caching with fetch
const data = await fetch(url, {
  next: { revalidate: 3600, tags: ['posts'] }
})

// ISR
export const revalidate = 60

// Revalidation
import { revalidateTag } from 'next/cache'
revalidateTag('posts')

EreoJS:

tsx
// Route-level caching
export const config = {
  cache: {
    maxAge: 3600,
    tags: ['posts']
  }
}

// Revalidation
import { revalidateTag } from '@ereo/data'
await revalidateTag('posts')

Key differences:

  • Next.js has implicit fetch caching
  • EreoJS requires explicit cache configuration
  • Both support tag-based invalidation

Streaming ​

Next.js:

tsx
import { Suspense } from 'react'

async function Comments({ postId }) {
  const comments = await getComments(postId)
  return <CommentList comments={comments} />
}

export default function Post({ params }) {
  return (
    <article>
      <PostContent id={params.id} />
      <Suspense fallback={<Loading />}>
        <Comments postId={params.id} />
      </Suspense>
    </article>
  )
}

EreoJS:

tsx
import { Suspense } from 'react'
import { defer } from '@ereo/data'
import { Await } from '@ereo/client'

export const loader = createLoader(async ({ params }) => {
  const post = await getPost(params.id)
  const comments = defer(getComments(params.id))
  return { post, comments }
})

export default function Post({ loaderData }) {
  return (
    <article>
      <PostContent post={loaderData.post} />
      <Suspense fallback={<Loading />}>
        <Await resolve={loaderData.comments}>
          {(comments) => <CommentList comments={comments} />}
        </Await>
      </Suspense>
    </article>
  )
}

Key differences:

  • Next.js streams async Server Components
  • EreoJS uses defer() with <Await> pattern
  • Both support React Suspense

Middleware ​

Next.js:

ts
// middleware.ts (root level only)
import { NextResponse } from 'next/server'

export function middleware(request) {
  if (!request.cookies.get('session')) {
    return NextResponse.redirect('/login')
  }
  return NextResponse.next()
}

export const config = {
  matcher: '/dashboard/:path*'
}

EreoJS:

ts
// routes/dashboard/_middleware.ts
export const middleware = async (request, context, next) => {
  if (!request.headers.get('Cookie')?.includes('session')) {
    return Response.redirect('/login')
  }
  return next()
}

Key differences:

  • Next.js has single middleware file with matchers
  • EreoJS has route-level middleware files
  • EreoJS middleware is more composable

Performance ​

Bundle Size ​

MetricEreoJSNext.js
Framework Runtime~15KB~85KB
Islands Overhead~2KB per islandFull component tree
React HydrationSelectiveFull page

Build Speed ​

EreoJS uses Bun's native bundler, which is significantly faster than Webpack:

Project SizeEreoJSNext.js (Webpack)Next.js (Turbopack)
Small (10 routes)~0.5s~3s~1.5s
Medium (50 routes)~2s~15s~5s
Large (200 routes)~5s~60s~15s

Runtime Performance ​

Both achieve similar runtime performance for SSR. EreoJS's advantage comes from:

  • Bun's faster JavaScript execution
  • Smaller client bundles with islands
  • Less hydration overhead

TypeScript Support ​

Both have excellent TypeScript support. Main differences:

Next.js:

  • Generates types for routes automatically
  • Server/Client boundary types
  • Complex configuration

EreoJS:

  • Simpler type inference
  • Explicit loader/action types
  • Manual type generation for routes

When to Choose EreoJS ​

  • You want explicit control over caching
  • You prefer islands architecture over full hydration
  • You're using Bun or want faster builds
  • You value simplicity over features
  • You're coming from Remix and want similar patterns

When to Choose Next.js ​

  • You need the Vercel ecosystem integration
  • You want React Server Components
  • You prefer implicit caching and conventions
  • You need the larger community and ecosystem
  • You're already invested in the Next.js way

Migration Path ​

See the migration guide for detailed instructions on migrating from Next.js to EreoJS.

Released under the MIT License.