diff --git a/.gitignore b/.gitignore
index 61a20a6..4b30b1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
yarn.lock
node_modules
+.react-router/
+
# Editor Configs
.idea
.vscode
diff --git a/bun.lockb b/bun.lockb
index 40c4f73..180b0ce 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 33636b7..fa49fc4 100644
--- a/package.json
+++ b/package.json
@@ -22,5 +22,5 @@
"@biomejs/biome": "^1.9.4",
"typescript": "^5.7.2"
},
- "trustedDependencies": ["@biomejs/biome"]
+ "trustedDependencies": ["@biomejs/biome", "esbuild"]
}
diff --git a/packages/www/app/components/misc/client-hints.tsx b/packages/www/app/components/misc/client-hints.tsx
index 06d48c3..322b4d7 100644
--- a/packages/www/app/components/misc/client-hints.tsx
+++ b/packages/www/app/components/misc/client-hints.tsx
@@ -1,5 +1,5 @@
import { useEffect } from 'react'
-import { useRevalidator } from '@remix-run/react'
+import { useRevalidator } from 'react-router';
import { subscribeToSchemeChange } from '@epic-web/client-hints/color-scheme'
import { hintsUtils } from '#app/utils/hooks/use-hints'
diff --git a/packages/www/app/components/misc/error-boundary.tsx b/packages/www/app/components/misc/error-boundary.tsx
index 98c129b..6e29f90 100644
--- a/packages/www/app/components/misc/error-boundary.tsx
+++ b/packages/www/app/components/misc/error-boundary.tsx
@@ -1,5 +1,5 @@
-import type { ErrorResponse } from '@remix-run/router'
-import { isRouteErrorResponse, useParams, useRouteError } from '@remix-run/react'
+import type { ErrorResponse } from 'react-router';
+import { isRouteErrorResponse, useParams, useRouteError } from 'react-router';
type StatusHandler = (info: {
error: ErrorResponse
diff --git a/packages/www/app/components/misc/language-switcher.tsx b/packages/www/app/components/misc/language-switcher.tsx
index dee2a77..2895b21 100644
--- a/packages/www/app/components/misc/language-switcher.tsx
+++ b/packages/www/app/components/misc/language-switcher.tsx
@@ -1,4 +1,4 @@
-import { useNavigate } from '@remix-run/react'
+import { useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next'
import { LucideLanguages } from 'lucide-react'
import {
diff --git a/packages/www/app/components/misc/theme-switcher.tsx b/packages/www/app/components/misc/theme-switcher.tsx
index 03eb4dd..394c041 100644
--- a/packages/www/app/components/misc/theme-switcher.tsx
+++ b/packages/www/app/components/misc/theme-switcher.tsx
@@ -1,5 +1,5 @@
import type { Theme, ThemeExtended } from '#app/utils/hooks/use-theme'
-import { useSubmit } from '@remix-run/react'
+import { useSubmit } from 'react-router';
import { LucideSun, LucideMoon, LucideMonitor } from 'lucide-react'
import { useOptimisticThemeMode } from '#app/utils/hooks/use-theme'
import { cn } from '#app/utils/misc'
diff --git a/packages/www/app/components/navigation.tsx b/packages/www/app/components/navigation.tsx
index ae4b27b..f857cd4 100644
--- a/packages/www/app/components/navigation.tsx
+++ b/packages/www/app/components/navigation.tsx
@@ -1,4 +1,4 @@
-import { Link, useLocation, useNavigate, useSubmit } from '@remix-run/react'
+import { Link, useLocation, useNavigate, useSubmit } from 'react-router';
import {
LucideCheck,
LucideChevronDown,
diff --git a/packages/www/app/entry.client.tsx b/packages/www/app/entry.client.tsx
index 7a880f6..c62c264 100644
--- a/packages/www/app/entry.client.tsx
+++ b/packages/www/app/entry.client.tsx
@@ -1,4 +1,4 @@
-import { RemixBrowser } from '@remix-run/react'
+import { HydratedRouter } from 'react-router/dom';
import i18next from 'i18next'
import I18nextBrowserLanguageDetector from 'i18next-browser-languagedetector'
import { startTransition } from 'react'
@@ -27,7 +27,7 @@ async function main() {
hydrateRoot(
document,
-
+
,
)
})
diff --git a/packages/www/app/entry.server.tsx b/packages/www/app/entry.server.tsx
index 7c4daba..0b1034f 100644
--- a/packages/www/app/entry.server.tsx
+++ b/packages/www/app/entry.server.tsx
@@ -1,9 +1,9 @@
-import type { AppLoadContext, EntryContext } from '@remix-run/node'
+import type { AppLoadContext, EntryContext } from 'react-router';
import { isbot } from 'isbot'
import { PassThrough } from 'node:stream'
import crypto from 'node:crypto'
-import { RemixServer } from '@remix-run/react'
-import { createReadableStreamFromReadable } from '@remix-run/node'
+import { ServerRouter } from 'react-router';
+import { createReadableStreamFromReadable } from '@react-router/node';
import { renderToPipeableStream } from 'react-dom/server'
import { createInstance } from 'i18next'
import { I18nextProvider, initReactI18next } from 'react-i18next'
@@ -17,7 +17,7 @@ export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
- remixContext: EntryContext,
+ reactRouterContext: EntryContext,
_: AppLoadContext,
) {
const callbackName = isbot(request.headers.get('user-agent'))
@@ -40,7 +40,7 @@ export default async function handleRequest(
*/
const instance = createInstance()
const lng = await i18nServer.getLocale(request)
- const ns = i18nServer.getRouteNamespaces(remixContext)
+ const ns = i18nServer.getRouteNamespaces(reactRouterContext)
await instance.use(initReactI18next).init({
...i18n,
@@ -54,7 +54,7 @@ export default async function handleRequest(
const { pipe, abort } = renderToPipeableStream(
-
+
,
{
@@ -91,5 +91,5 @@ export default async function handleRequest(
)
setTimeout(abort, streamTimeout + 1000)
- })
+ });
}
diff --git a/packages/www/app/modules/auth/auth-session.server.ts b/packages/www/app/modules/auth/auth-session.server.ts
index 43edf9d..c442586 100644
--- a/packages/www/app/modules/auth/auth-session.server.ts
+++ b/packages/www/app/modules/auth/auth-session.server.ts
@@ -1,4 +1,4 @@
-import { createCookieSessionStorage } from '@remix-run/node'
+import { createCookieSessionStorage } from 'react-router';
import { Resource } from 'sst'
export const AUTH_SESSION_KEY = '_auth'
diff --git a/packages/www/app/modules/auth/auth.server.ts b/packages/www/app/modules/auth/auth.server.ts
index f7c826e..212aede 100644
--- a/packages/www/app/modules/auth/auth.server.ts
+++ b/packages/www/app/modules/auth/auth.server.ts
@@ -1,6 +1,6 @@
import { db, schema } from '@company/core/src/drizzle/index'
import { Email } from '@company/core/src/email/index'
-import { redirect } from '@remix-run/node'
+import { redirect } from 'react-router';
import { eq } from 'drizzle-orm'
import { Authenticator } from 'remix-auth'
import { GitHubStrategy } from 'remix-auth-github'
diff --git a/packages/www/app/modules/i18n/i18n.server.ts b/packages/www/app/modules/i18n/i18n.server.ts
index 91eda75..14ae638 100644
--- a/packages/www/app/modules/i18n/i18n.server.ts
+++ b/packages/www/app/modules/i18n/i18n.server.ts
@@ -1,4 +1,4 @@
-import { createCookie } from '@remix-run/node'
+import { createCookie } from 'react-router';
import { RemixI18Next } from 'remix-i18next/server'
import * as i18n from '#app/modules/i18n/i18n'
diff --git a/packages/www/app/root.tsx b/packages/www/app/root.tsx
index 20c54c8..2763bcd 100644
--- a/packages/www/app/root.tsx
+++ b/packages/www/app/root.tsx
@@ -1,14 +1,7 @@
-import type { MetaFunction, LinksFunction, LoaderFunctionArgs } from '@remix-run/node'
+import type { MetaFunction, LinksFunction, LoaderFunctionArgs } from 'react-router';
import type { Theme } from '#app/utils/hooks/use-theme'
-import {
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
- useLoaderData,
-} from '@remix-run/react'
-import { data } from '@remix-run/node'
+import { Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData } from 'react-router';
+import { data } from 'react-router';
import { useChangeLanguage } from 'remix-i18next/react'
import { AuthenticityTokenProvider } from 'remix-utils/csrf/react'
import { HoneypotProvider } from 'remix-utils/honeypot/react'
diff --git a/packages/www/app/routes.ts b/packages/www/app/routes.ts
index 46a09d6..0ed3fc9 100644
--- a/packages/www/app/routes.ts
+++ b/packages/www/app/routes.ts
@@ -1,5 +1,5 @@
-import type { RouteConfig } from '@remix-run/route-config'
-import { remixRoutesOptionAdapter } from '@remix-run/routes-option-adapter'
+import type { RouteConfig } from '@react-router/dev/routes';
+import { remixRoutesOptionAdapter } from '@react-router/remix-routes-option-adapter';
import { flatRoutes } from 'remix-flat-routes'
export default remixRoutesOptionAdapter((defineRoutes) =>
diff --git a/packages/www/app/routes/$.tsx b/packages/www/app/routes/$.tsx
index 4874f7e..c4d6d6a 100644
--- a/packages/www/app/routes/$.tsx
+++ b/packages/www/app/routes/$.tsx
@@ -1,5 +1,5 @@
-import type { MetaFunction } from '@remix-run/node'
-import { Link } from '@remix-run/react'
+import type { MetaFunction } from 'react-router';
+import { Link } from 'react-router';
import { LucideHelpCircle, LucideExternalLink } from 'lucide-react'
import { siteConfig } from '#app/utils/constants/brand'
import { GenericErrorBoundary } from '#app/components/misc/error-boundary'
diff --git a/packages/www/app/routes/_home+/_index.tsx b/packages/www/app/routes/_home+/_index.tsx
index 90f934f..b8f2add 100644
--- a/packages/www/app/routes/_home+/_index.tsx
+++ b/packages/www/app/routes/_home+/_index.tsx
@@ -1,5 +1,5 @@
-import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'
-import { Link, useLoaderData } from '@remix-run/react'
+import type { MetaFunction, LoaderFunctionArgs } from 'react-router';
+import { Link, useLoaderData } from 'react-router';
import Markdown from 'react-markdown'
import rehypeSlug from 'rehype-slug'
import { authenticator } from '#app/modules/auth/auth.server'
diff --git a/packages/www/app/routes/_home+/_layout.tsx b/packages/www/app/routes/_home+/_layout.tsx
index aa99a0a..c6deb4c 100644
--- a/packages/www/app/routes/_home+/_layout.tsx
+++ b/packages/www/app/routes/_home+/_layout.tsx
@@ -1,4 +1,4 @@
-import { Outlet } from '@remix-run/react'
+import { Outlet } from 'react-router';
export const ROUTE_PATH = '/' as const
diff --git a/packages/www/app/routes/admin+/_index.tsx b/packages/www/app/routes/admin+/_index.tsx
index 28b46b2..eaa0332 100644
--- a/packages/www/app/routes/admin+/_index.tsx
+++ b/packages/www/app/routes/admin+/_index.tsx
@@ -1,4 +1,4 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router';
import { requireUserWithRole } from '#app/utils/permissions.server'
export async function loader({ request }: LoaderFunctionArgs) {
diff --git a/packages/www/app/routes/admin+/_layout.tsx b/packages/www/app/routes/admin+/_layout.tsx
index d05b2c7..f9fcc9f 100644
--- a/packages/www/app/routes/admin+/_layout.tsx
+++ b/packages/www/app/routes/admin+/_layout.tsx
@@ -1,5 +1,5 @@
-import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'
-import { useLoaderData } from '@remix-run/react'
+import type { MetaFunction, LoaderFunctionArgs } from 'react-router';
+import { useLoaderData } from 'react-router';
import { LucideShoppingBasket, LucideExternalLink } from 'lucide-react'
import { requireUserWithRole } from '#app/utils/permissions.server'
import { cn } from '#app/utils/misc.js'
diff --git a/packages/www/app/routes/auth+/$provider.callback.tsx b/packages/www/app/routes/auth+/$provider.callback.tsx
index ffd2aef..a0074e9 100644
--- a/packages/www/app/routes/auth+/$provider.callback.tsx
+++ b/packages/www/app/routes/auth+/$provider.callback.tsx
@@ -1,4 +1,4 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router';
import { authenticator } from '#app/modules/auth/auth.server'
import { ROUTE_PATH as LOGIN_PATH } from '#app/routes/auth+/login'
import { ROUTE_PATH as DASHBOARD_PATH } from '#app/routes/dashboard+/_layout'
diff --git a/packages/www/app/routes/auth+/$provider.tsx b/packages/www/app/routes/auth+/$provider.tsx
index d63a3ae..0c6ae24 100644
--- a/packages/www/app/routes/auth+/$provider.tsx
+++ b/packages/www/app/routes/auth+/$provider.tsx
@@ -1,5 +1,5 @@
-import type { ActionFunctionArgs } from '@remix-run/node'
-import { redirect } from '@remix-run/node'
+import type { ActionFunctionArgs } from 'react-router';
+import { redirect } from 'react-router';
import { authenticator } from '#app/modules/auth/auth.server'
import { ROUTE_PATH as LOGIN_PATH } from '#app/routes/auth+/login'
diff --git a/packages/www/app/routes/auth+/_layout.tsx b/packages/www/app/routes/auth+/_layout.tsx
index 7807b2d..55f1f31 100644
--- a/packages/www/app/routes/auth+/_layout.tsx
+++ b/packages/www/app/routes/auth+/_layout.tsx
@@ -1,6 +1,6 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
-import { Link, Outlet } from '@remix-run/react'
-import { redirect } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router';
+import { Link, Outlet } from 'react-router';
+import { redirect } from 'react-router';
import { authenticator } from '#app/modules/auth/auth.server'
import { getDomainPathname } from '#app/utils/misc.server'
import { ROUTE_PATH as HOME_PATH } from '#app/routes/_home+/_layout'
diff --git a/packages/www/app/routes/auth+/login.tsx b/packages/www/app/routes/auth+/login.tsx
index fcc6211..dbc8a7a 100644
--- a/packages/www/app/routes/auth+/login.tsx
+++ b/packages/www/app/routes/auth+/login.tsx
@@ -1,11 +1,7 @@
-import type {
- MetaFunction,
- LoaderFunctionArgs,
- ActionFunctionArgs,
-} from '@remix-run/node'
+import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from 'react-router';
import { useRef, useEffect } from 'react'
-import { Form, useLoaderData } from '@remix-run/react'
-import { data } from '@remix-run/node'
+import { Form, useLoaderData } from 'react-router';
+import { data } from 'react-router';
import { useHydrated } from 'remix-utils/use-hydrated'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
diff --git a/packages/www/app/routes/auth+/logout.tsx b/packages/www/app/routes/auth+/logout.tsx
index e632951..c9fba32 100644
--- a/packages/www/app/routes/auth+/logout.tsx
+++ b/packages/www/app/routes/auth+/logout.tsx
@@ -1,4 +1,4 @@
-import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node'
+import type { LoaderFunctionArgs, ActionFunctionArgs } from 'react-router';
import { authenticator } from '#app/modules/auth/auth.server'
export const ROUTE_PATH = '/auth/logout' as const
diff --git a/packages/www/app/routes/auth+/magic-link.tsx b/packages/www/app/routes/auth+/magic-link.tsx
index 1045dde..0cb336e 100644
--- a/packages/www/app/routes/auth+/magic-link.tsx
+++ b/packages/www/app/routes/auth+/magic-link.tsx
@@ -1,4 +1,4 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router';
import { authenticator } from '#app/modules/auth/auth.server'
import { ROUTE_PATH as DASHBOARD_PATH } from '#app/routes/dashboard+/_layout'
diff --git a/packages/www/app/routes/auth+/verify.tsx b/packages/www/app/routes/auth+/verify.tsx
index 4ab7fce..c873d73 100644
--- a/packages/www/app/routes/auth+/verify.tsx
+++ b/packages/www/app/routes/auth+/verify.tsx
@@ -1,11 +1,7 @@
-import type {
- MetaFunction,
- LoaderFunctionArgs,
- ActionFunctionArgs,
-} from '@remix-run/node'
+import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from 'react-router';
import { useRef, useEffect } from 'react'
-import { Form, useLoaderData } from '@remix-run/react'
-import { data, redirect } from '@remix-run/node'
+import { Form, useLoaderData } from 'react-router';
+import { data, redirect } from 'react-router';
import { useHydrated } from 'remix-utils/use-hydrated'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
diff --git a/packages/www/app/routes/dashboard+/_index.tsx b/packages/www/app/routes/dashboard+/_index.tsx
index 5886a60..11b0652 100644
--- a/packages/www/app/routes/dashboard+/_index.tsx
+++ b/packages/www/app/routes/dashboard+/_index.tsx
@@ -1,4 +1,4 @@
-import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'
+import type { MetaFunction, LoaderFunctionArgs } from 'react-router';
import { LucidePlus, LucideExternalLink } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { requireUser } from '#app/modules/auth/auth.server'
diff --git a/packages/www/app/routes/dashboard+/_layout.tsx b/packages/www/app/routes/dashboard+/_layout.tsx
index fc6389f..9b8ef45 100644
--- a/packages/www/app/routes/dashboard+/_layout.tsx
+++ b/packages/www/app/routes/dashboard+/_layout.tsx
@@ -1,6 +1,6 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
-import { Outlet, useLoaderData } from '@remix-run/react'
-import { redirect } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router';
+import { Outlet, useLoaderData } from 'react-router';
+import { redirect } from 'react-router';
import { requireUser } from '#app/modules/auth/auth.server'
import { ROUTE_PATH as ONBOARDING_USERNAME_PATH } from '#app/routes/onboarding+/username'
import { Navigation } from '#app/components/navigation'
diff --git a/packages/www/app/routes/dashboard+/checkout.tsx b/packages/www/app/routes/dashboard+/checkout.tsx
index f8f1dbd..622eb29 100644
--- a/packages/www/app/routes/dashboard+/checkout.tsx
+++ b/packages/www/app/routes/dashboard+/checkout.tsx
@@ -1,7 +1,7 @@
-import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'
+import type { MetaFunction, LoaderFunctionArgs } from 'react-router';
import { useState } from 'react'
-import { Link, useLoaderData, useRevalidator } from '@remix-run/react'
-import { redirect } from '@remix-run/node'
+import { Link, useLoaderData, useRevalidator } from 'react-router';
+import { redirect } from 'react-router';
import {
LucideLoader2,
LucideBadgeCheck,
diff --git a/packages/www/app/routes/dashboard+/settings.billing.tsx b/packages/www/app/routes/dashboard+/settings.billing.tsx
index a70986d..72a71ba 100644
--- a/packages/www/app/routes/dashboard+/settings.billing.tsx
+++ b/packages/www/app/routes/dashboard+/settings.billing.tsx
@@ -1,12 +1,8 @@
-import type {
- MetaFunction,
- LoaderFunctionArgs,
- ActionFunctionArgs,
-} from '@remix-run/node'
+import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from 'react-router';
import type { Interval, Plan as PlanEnum } from '@company/core/src/constants'
import { useState } from 'react'
-import { Form, useLoaderData } from '@remix-run/react'
-import { redirect } from '@remix-run/node'
+import { Form, useLoaderData } from 'react-router';
+import { redirect } from 'react-router';
import { requireSessionUser } from '#app/modules/auth/auth.server'
import { PLANS, PRICING_PLANS, INTERVALS, CURRENCIES } from '@company/core/src/constants'
import { getLocaleCurrency } from '#app/utils/misc.server'
diff --git a/packages/www/app/routes/dashboard+/settings.index.tsx b/packages/www/app/routes/dashboard+/settings.index.tsx
index ee40c8f..e26996c 100644
--- a/packages/www/app/routes/dashboard+/settings.index.tsx
+++ b/packages/www/app/routes/dashboard+/settings.index.tsx
@@ -1,7 +1,7 @@
-import type { LoaderFunctionArgs, ActionFunctionArgs } from '@remix-run/node'
+import type { LoaderFunctionArgs, ActionFunctionArgs } from 'react-router';
import { useRef, useState } from 'react'
-import { Form, useFetcher, useLoaderData, useActionData } from '@remix-run/react'
-import { data, redirect } from '@remix-run/node'
+import { Form, useFetcher, useLoaderData, useActionData } from 'react-router';
+import { data, redirect } from 'react-router';
import { z } from 'zod'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
diff --git a/packages/www/app/routes/dashboard+/settings.tsx b/packages/www/app/routes/dashboard+/settings.tsx
index c41e9de..907ea89 100644
--- a/packages/www/app/routes/dashboard+/settings.tsx
+++ b/packages/www/app/routes/dashboard+/settings.tsx
@@ -1,5 +1,5 @@
-import type { MetaFunction, LoaderFunctionArgs } from '@remix-run/node'
-import { Link, Outlet, useLocation } from '@remix-run/react'
+import type { MetaFunction, LoaderFunctionArgs } from 'react-router';
+import { Link, Outlet, useLocation } from 'react-router';
import { z } from 'zod'
import { requireUser } from '#app/modules/auth/auth.server'
import { cn } from '#app/utils/misc'
diff --git a/packages/www/app/routes/onboarding+/_layout.tsx b/packages/www/app/routes/onboarding+/_layout.tsx
index cdd87c5..8b1a5b9 100644
--- a/packages/www/app/routes/onboarding+/_layout.tsx
+++ b/packages/www/app/routes/onboarding+/_layout.tsx
@@ -1,6 +1,6 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
-import { Outlet } from '@remix-run/react'
-import { json, redirect } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router'
+import { Outlet } from 'react-router'
+import { redirect } from 'react-router'
import { requireUser } from '#app/modules/auth/auth.server'
import { getDomainPathname } from '#app/utils/misc.server'
import { ROUTE_PATH as DASHBOARD_PATH } from '#app/routes/dashboard+/_layout'
diff --git a/packages/www/app/routes/onboarding+/username.tsx b/packages/www/app/routes/onboarding+/username.tsx
index d8a86d2..44eac4f 100644
--- a/packages/www/app/routes/onboarding+/username.tsx
+++ b/packages/www/app/routes/onboarding+/username.tsx
@@ -1,11 +1,7 @@
-import type {
- MetaFunction,
- LoaderFunctionArgs,
- ActionFunctionArgs,
-} from '@remix-run/node'
+import type { MetaFunction, LoaderFunctionArgs, ActionFunctionArgs } from 'react-router';
import { useRef, useEffect } from 'react'
-import { Form, useActionData } from '@remix-run/react'
-import { data, redirect } from '@remix-run/node'
+import { Form, useActionData } from 'react-router';
+import { data, redirect } from 'react-router';
import { useHydrated } from 'remix-utils/use-hydrated'
import { AuthenticityTokenInput } from 'remix-utils/csrf/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
diff --git a/packages/www/app/routes/resources+/reset-image.ts b/packages/www/app/routes/resources+/reset-image.ts
index 3eac677..fd51061 100644
--- a/packages/www/app/routes/resources+/reset-image.ts
+++ b/packages/www/app/routes/resources+/reset-image.ts
@@ -1,4 +1,4 @@
-import type { ActionFunctionArgs } from '@remix-run/router'
+import type { ActionFunctionArgs } from 'react-router';
import { requireUser } from '#app/modules/auth/auth.server'
import { db, schema } from '@company/core/src/drizzle/index'
import { eq } from 'drizzle-orm'
diff --git a/packages/www/app/routes/resources+/update-theme.ts b/packages/www/app/routes/resources+/update-theme.ts
index 14218f3..99103d4 100644
--- a/packages/www/app/routes/resources+/update-theme.ts
+++ b/packages/www/app/routes/resources+/update-theme.ts
@@ -1,5 +1,5 @@
-import type { ActionFunctionArgs } from '@remix-run/node'
-import { redirect } from '@remix-run/node'
+import type { ActionFunctionArgs } from 'react-router';
+import { redirect } from 'react-router';
import { safeRedirect } from 'remix-utils/safe-redirect'
import { ThemeSchema, setTheme } from '#app/utils/hooks/use-theme'
diff --git a/packages/www/app/routes/resources+/upload-image.ts b/packages/www/app/routes/resources+/upload-image.ts
index 557a892..0926ee0 100644
--- a/packages/www/app/routes/resources+/upload-image.ts
+++ b/packages/www/app/routes/resources+/upload-image.ts
@@ -1,10 +1,5 @@
-import type { ActionFunctionArgs } from '@remix-run/router'
-import {
- unstable_createMemoryUploadHandler,
- unstable_parseMultipartFormData,
- MaxPartSizeExceededError,
- data,
-} from '@remix-run/node'
+import { data, type ActionFunctionArgs } from 'react-router'
+import { parseFormData, type FileUpload } from '@mjackson/form-data-parser'
import { z } from 'zod'
import { parseWithZod } from '@conform-to/zod'
import type { SubmissionResult } from '@conform-to/react'
@@ -13,6 +8,12 @@ import { createToastHeaders } from '#app/utils/toast.server'
import { db, schema } from '@company/core/src/drizzle/index'
import { eq } from 'drizzle-orm'
+export class MaxPartSizeExceededError extends Error {
+ constructor() {
+ super('File size exceeded the maximum allowed size')
+ }
+}
+
export const ROUTE_PATH = '/resources/upload-image' as const
export const MAX_FILE_SIZE = 1024 * 1024 * 3 // 3MB
@@ -20,14 +21,20 @@ export const ImageSchema = z.object({
imageFile: z.instanceof(File).refine((file) => file.size > 0, 'Image is required.'),
})
-export async function action({ request, context }: ActionFunctionArgs) {
+export async function action({ request }: ActionFunctionArgs) {
try {
const user = await requireUser(request)
- const formData = await unstable_parseMultipartFormData(
- request,
- unstable_createMemoryUploadHandler({ maxPartSize: MAX_FILE_SIZE }),
- )
+ const formData = await parseFormData(request, async (fileUpload: FileUpload) => {
+ const buffer = await fileUpload.arrayBuffer()
+ if (buffer.byteLength > MAX_FILE_SIZE) {
+ throw new MaxPartSizeExceededError()
+ }
+ return new File([buffer], fileUpload.name, {
+ type: fileUpload.type,
+ })
+ })
+
const submission = await parseWithZod(formData, {
schema: ImageSchema.transform(async (data) => {
return {
@@ -39,6 +46,7 @@ export async function action({ request, context }: ActionFunctionArgs) {
}),
async: true,
})
+
if (submission.status !== 'success') {
return data(submission.reply(), {
status: submission.status === 'error' ? 400 : 200,
diff --git a/packages/www/app/routes/resources+/user-images.$imageId.ts b/packages/www/app/routes/resources+/user-images.$imageId.ts
index 366d48b..75efadc 100644
--- a/packages/www/app/routes/resources+/user-images.$imageId.ts
+++ b/packages/www/app/routes/resources+/user-images.$imageId.ts
@@ -1,4 +1,4 @@
-import type { LoaderFunctionArgs } from '@remix-run/node'
+import type { LoaderFunctionArgs } from 'react-router';
import { db, schema } from '@company/core/src/drizzle/index'
import { eq } from 'drizzle-orm'
diff --git a/packages/www/app/utils/csrf.server.ts b/packages/www/app/utils/csrf.server.ts
index eb24542..27f9c35 100644
--- a/packages/www/app/utils/csrf.server.ts
+++ b/packages/www/app/utils/csrf.server.ts
@@ -2,7 +2,7 @@
* Learn more about CSRF protection:
* @see https://github.com/sergiodxa/remix-utils?tab=readme-ov-file#csrf
*/
-import { createCookie } from '@remix-run/node'
+import { createCookie } from 'react-router';
import { CSRF, CSRFError } from 'remix-utils/csrf/server'
import { Resource } from 'sst'
diff --git a/packages/www/app/utils/hooks/use-request-info.ts b/packages/www/app/utils/hooks/use-request-info.ts
index 77f669b..09775e1 100644
--- a/packages/www/app/utils/hooks/use-request-info.ts
+++ b/packages/www/app/utils/hooks/use-request-info.ts
@@ -1,5 +1,5 @@
import type { loader as rootLoader } from '#app/root'
-import { useRouteLoaderData } from '@remix-run/react'
+import { useRouteLoaderData } from 'react-router';
/**
* Returns the request info from the Root loader.
diff --git a/packages/www/app/utils/hooks/use-theme.ts b/packages/www/app/utils/hooks/use-theme.ts
index 6fe357b..4f81cfc 100644
--- a/packages/www/app/utils/hooks/use-theme.ts
+++ b/packages/www/app/utils/hooks/use-theme.ts
@@ -3,7 +3,7 @@
*/
import * as cookie from 'cookie'
import { z } from 'zod'
-import { useFetcher } from '@remix-run/react'
+import { useFetcher } from 'react-router';
import { useHints } from '#app/utils/hooks/use-hints'
import { useRequestInfo } from '#app/utils/hooks/use-request-info'
diff --git a/packages/www/app/utils/misc.ts b/packages/www/app/utils/misc.ts
index 82c351a..ec80af5 100644
--- a/packages/www/app/utils/misc.ts
+++ b/packages/www/app/utils/misc.ts
@@ -1,7 +1,6 @@
-import type { SerializeFrom } from '@remix-run/node'
import type { ClassValue } from 'clsx'
import type { loader as rootLoader } from '#app/root'
-import { useFormAction, useNavigation, useRouteLoaderData } from '@remix-run/react'
+import { useFormAction, useNavigation, useRouteLoaderData } from 'react-router'
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
@@ -18,7 +17,7 @@ export function cn(...inputs: ClassValue[]) {
*/
// biome-ignore lint/suspicious/noExplicitAny:
-function isUser(user: any): user is SerializeFrom['user'] {
+function isUser(user: any): boolean {
return user && typeof user === 'object' && typeof user.id === 'string'
}
diff --git a/packages/www/app/utils/permissions.server.ts b/packages/www/app/utils/permissions.server.ts
index c1ecf2a..5cc5591 100644
--- a/packages/www/app/utils/permissions.server.ts
+++ b/packages/www/app/utils/permissions.server.ts
@@ -2,7 +2,7 @@
* Permissions and Roles.
* Implementation based on github.com/epicweb-dev/epic-stack
*/
-import { data } from '@remix-run/node'
+import { data } from 'react-router';
import { requireUser } from '#app/modules/auth/auth.server'
import { userHasRole } from '#app/utils/misc'
import { ROUTE_PATH as LOGIN_PATH } from '#app/routes/auth+/login'
diff --git a/packages/www/app/utils/toast.server.ts b/packages/www/app/utils/toast.server.ts
index 134e974..01c5ce6 100644
--- a/packages/www/app/utils/toast.server.ts
+++ b/packages/www/app/utils/toast.server.ts
@@ -2,7 +2,7 @@
* Server-Side Toasts.
* Implementation based on github.com/epicweb-dev/epic-stack
*/
-import { redirect, createCookieSessionStorage } from '@remix-run/node'
+import { redirect, createCookieSessionStorage } from 'react-router';
import { z } from 'zod'
import { combineHeaders } from '#app/utils/misc.server'
import { Resource } from 'sst'
diff --git a/packages/www/package.json b/packages/www/package.json
index 90486cf..998d4ed 100644
--- a/packages/www/package.json
+++ b/packages/www/package.json
@@ -7,25 +7,24 @@
"#*": "./*"
},
"scripts": {
- "build": "remix vite:build",
- "dev": "remix vite:dev",
+ "build": "react-router build",
+ "dev": "react-router dev",
"start": "sst shell remix vite:start",
"test": "vitest",
- "typecheck": "tsc"
+ "typecheck": "react-router typegen && tsc"
},
"dependencies": {
"@conform-to/react": "1.1.4",
"@conform-to/zod": "1.1.3",
"@epic-web/client-hints": "^1.3.2",
+ "@mjackson/form-data-parser": "^0.5.1",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@react-email/components": "^0.0.28",
"@react-email/render": "^1.0.2",
- "@remix-run/node": "^2.15.0",
- "@remix-run/react": "^2.15.0",
- "@remix-run/router": "^1.21.0",
+ "@react-router/node": "^7.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cookie": "^1.0.2",
@@ -39,10 +38,12 @@
"react-dom": "^18.3.1",
"react-i18next": "^15.1.1",
"react-markdown": "^9.0.1",
+ "react-router": "^7.1.1",
+ "react-router-dom": "^7.1.1",
"remix-auth": "^3.6.0",
"remix-auth-github": "^1.7.0",
"remix-auth-totp": "^3.3.0",
- "remix-i18next": "^6.1.0",
+ "remix-i18next": "^7.0.1",
"remix-utils": "^7.6.0",
"sonner": "^1.4.41",
"sst": "^3.3.27",
@@ -51,8 +52,8 @@
"zod": "^3.23.6"
},
"devDependencies": {
- "@remix-run/dev": "^2.15.0",
- "@remix-run/routes-option-adapter": "^2.15.0",
+ "@react-router/dev": "^7.0.0",
+ "@react-router/remix-routes-option-adapter": "^7.0.0",
"@tailwindcss/typography": "^0.5.15",
"@types/cookie": "^1.0.0",
"@types/react": "^18.3.2",
@@ -60,7 +61,6 @@
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"rehype-slug": "^6.0.0",
- "remix-development-tools": "^4.1.6",
"remix-flat-routes": "^0.6.5",
"tailwindcss": "^3.4.3",
"typescript": "^5.7.2",
diff --git a/packages/www/tests/setup-test-env.ts b/packages/www/tests/setup-test-env.ts
deleted file mode 100644
index 50d3111..0000000
--- a/packages/www/tests/setup-test-env.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { installGlobals } from '@remix-run/node'
-
-installGlobals()
-
-// Handle `beforeEach` | `afterEach` and other setup/teardown logic here.
-// afterEach(() => resetHandlers())
-// afterEach(() => cleanup())
diff --git a/packages/www/tsconfig.json b/packages/www/tsconfig.json
index 49c63fc..2c9d09b 100644
--- a/packages/www/tsconfig.json
+++ b/packages/www/tsconfig.json
@@ -8,8 +8,16 @@
"**/.client/**/*.tsx"
],
"compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
- "types": ["@remix-run/node", "vite/client", "vitest/globals"],
+ "lib": [
+ "DOM",
+ "DOM.Iterable",
+ "ES2022"
+ ],
+ "types": [
+ "@react-router/node",
+ "vite/client",
+ "vitest/globals"
+ ],
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
@@ -26,7 +34,9 @@
"noEmit": true,
"baseUrl": ".",
"paths": {
- "#*": ["./*"]
+ "#*": [
+ "./*"
+ ]
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/www/vite.config.ts b/packages/www/vite.config.ts
index 55a4ea3..edcbf9c 100644
--- a/packages/www/vite.config.ts
+++ b/packages/www/vite.config.ts
@@ -1,31 +1,10 @@
-import { vitePlugin as remix } from '@remix-run/dev'
+import { reactRouter } from '@react-router/dev/vite'
import { defineConfig } from 'vite'
-import { remixDevTools } from 'remix-development-tools'
import tsconfigPaths from 'vite-tsconfig-paths'
-declare module '@remix-run/node' {
- // or cloudflare, deno, etc.
- interface Future {
- v3_singleFetch: true
- }
-}
-
export default defineConfig({
build: {
target: 'ES2022',
},
- plugins: [
- remixDevTools(),
- remix({
- future: {
- v3_fetcherPersist: true,
- v3_relativeSplatPath: true,
- v3_throwAbortReason: true,
- v3_lazyRouteDiscovery: true,
- v3_singleFetch: true,
- v3_routeConfig: true,
- },
- }),
- tsconfigPaths(),
- ],
+ plugins: [reactRouter(), tsconfigPaths()],
})
diff --git a/packages/www/vitest.config.ts b/packages/www/vitest.config.ts
index a52fbf8..bbd1644 100644
--- a/packages/www/vitest.config.ts
+++ b/packages/www/vitest.config.ts
@@ -7,9 +7,6 @@ import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths()],
test: {
- // Path to setup file that runs before your tests.
- setupFiles: ['./tests/setup-test-env.ts'],
- // Path to your test files.
include: ['./tests/integration/*.test.ts'],
},
})
diff --git a/readme.md b/readme.md
index 582e3a4..cc6d56c 100644
--- a/readme.md
+++ b/readme.md
@@ -1,4 +1,4 @@
-![](./.github/cover.png)
+![](https://github.com/Murderlon/the-startup-stack/blob/main/.github/cover.png?raw=true)
# The Startup Stack
@@ -6,7 +6,7 @@ Get independence from expensive SaaS without losing its developer experience,
the infra primitives to adapt to any future requirement, and the tools to build
delightful, secure user experiences.
-Check it live 👉 https://stack.merlijn.site
+Check it live 👉
## Contents
@@ -25,6 +25,7 @@ Check it live 👉 https://stack.merlijn.site
- [Stripe](#stripe)
- [Secrets](#secrets)
- [Use](#use)
+ - [Commands](#commands)
- [Authentication](#authentication)
- [Subscriptions](#subscriptions)
- [Internationalization](#internationalization)
@@ -35,7 +36,7 @@ Check it live 👉 https://stack.merlijn.site
## Features
-- **[Remix][]** as the full-stack **[React][]** framework.
+- **[React Router][]** (formerly known as [Remix][]) as the full-stack **[React][]** framework.
- **[SST][]** for infrastructure as code on AWS and Cloudflare.
- **[Hono][]** API on [AWS Lambda][].
- **[Postgres][]** database through **[Neon][]**.
@@ -400,6 +401,7 @@ Usage is as simple as it can be, as everything is already set up for you.
[Remix]: https://remix.run
+[React Router]: https://reactrouter.com
[React]: https://react.dev
[SST]: https://sst.dev
[Postgres]: https://postgresql.org