Skip to content

Migrating from Remix

Remix and EreoJS share many concepts, making migration straightforward.

Loaders

Remix:

tsx
import { json } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'

export async function loader({ params }) {
  const post = await getPost(params.id)
  return json({ post })
}

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

EreoJS:

tsx
import { createLoader } from '@ereo/data'
import { useLoaderData } from '@ereo/client'

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

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

Actions

Remix:

tsx
import { redirect } from '@remix-run/node'
import { Form } from '@remix-run/react'

export async function action({ request }) {
  const formData = await request.formData()
  await createPost(Object.fromEntries(formData))
  return redirect('/posts')
}

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

EreoJS:

tsx
import { createAction, redirect } from '@ereo/data'
import { Form } from '@ereo/client'

export const action = createAction(async ({ request }) => {
  const formData = await request.formData()
  await createPost(Object.fromEntries(formData))
  return redirect('/posts')
})

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

useFetcher

Remix:

tsx
import { useFetcher } from '@remix-run/react'

function LikeButton({ postId }) {
  const fetcher = useFetcher()
  return (
    <fetcher.Form method="post" action="/api/like">
      <input type="hidden" name="postId" value={postId} />
      <button type="submit">Like</button>
    </fetcher.Form>
  )
}

EreoJS:

tsx
import { useFetcher } from '@ereo/client'

function LikeButton({ postId }) {
  const fetcher = useFetcher()
  return (
    <fetcher.Form method="post" action="/api/like">
      <input type="hidden" name="postId" value={postId} />
      <button type="submit">Like</button>
    </fetcher.Form>
  )
}

useNavigation

Remix:

tsx
import { useNavigation } from '@remix-run/react'

function SubmitButton() {
  const navigation = useNavigation()
  const isSubmitting = navigation.state === 'submitting'
  return <button disabled={isSubmitting}>Submit</button>
}

EreoJS:

tsx
import { useNavigation } from '@ereo/client'

function SubmitButton() {
  const navigation = useNavigation()
  // Note: EreoJS uses `status` instead of Remix's `state`
  const isSubmitting = navigation.status === 'submitting'
  return <button disabled={isSubmitting}>Submit</button>
}

Error Boundaries

Remix:

tsx
import { useRouteError, isRouteErrorResponse } from '@remix-run/react'

export function ErrorBoundary() {
  const error = useRouteError()

  if (isRouteErrorResponse(error)) {
    return <h1>{error.status}: {error.statusText}</h1>
  }

  return <h1>Something went wrong</h1>
}

EreoJS:

tsx
import { useRouteError, isRouteErrorResponse } from '@ereo/client'

export function ErrorBoundary() {
  const error = useRouteError()

  if (isRouteErrorResponse(error)) {
    return <h1>{error.status}: {error.statusText}</h1>
  }

  return <h1>Something went wrong</h1>
}

Meta

Remix:

tsx
export function meta({ data }) {
  return [
    { title: data.post.title },
    { name: 'description', content: data.post.excerpt }
  ]
}

EreoJS:

tsx
export function meta({ data }) {
  return [
    { title: data.post.title },
    { name: 'description', content: data.post.excerpt }
  ]
}

Key Differences

FeatureRemixEreoJS
RuntimeNode.js (adapters)Bun (native)
Client InteractivityFull hydrationIslands (selective)
BundleresbuildBun
StreamingOptionalDefault

Migration Checklist

  1. Install Bun if not already installed
  2. Create new EreoJS project and copy routes
  3. Update imports from @remix-run/* to @ereo/*
  4. Convert loaders — remove json() wrapper, use createLoader
  5. Convert actions — use createAction
  6. Convert client components to islands
  7. Update navigationstatestatus
  8. Update configuration to ereo.config.ts
  9. Test all routes and functionality
  10. Deploy to Bun-compatible hosting

Getting Help

Released under the MIT License.