diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml
index 2b9b15ea..fe6575e1 100644
--- a/.github/workflows/publish-snapshot.yml
+++ b/.github/workflows/publish-snapshot.yml
@@ -9,7 +9,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- - run: corepack enable
+ - run: npm install -g corepack@latest && corepack enable
- uses: actions/setup-node@v4
with:
node-version: 20
diff --git a/apps/site/.env.example b/apps/site/.env.example
index 71272a7e..3d7dacfa 100644
--- a/apps/site/.env.example
+++ b/apps/site/.env.example
@@ -1,3 +1,4 @@
DOCSEARCH_APP_ID=RETR9S9VHS
DOCSEARCH_API_KEY=326c1723a310dfe29004b47608709907
DOCSEARCH_INDEX_NAME=tailwindui-protocol
+GITHUB_TOKEN=foo
diff --git a/apps/site/.gitignore b/apps/site/.gitignore
index 4b6044de..077123d3 100644
--- a/apps/site/.gitignore
+++ b/apps/site/.gitignore
@@ -39,5 +39,5 @@ node_modules
/public/build
.env
/.wrangler
-
+.react-router/
.dev.vars
diff --git a/apps/site/app/components/Button.tsx b/apps/site/app/components/Button.tsx
index cdca49ae..3ba19e05 100644
--- a/apps/site/app/components/Button.tsx
+++ b/apps/site/app/components/Button.tsx
@@ -1,4 +1,4 @@
-import { Link } from "@remix-run/react";
+import { Link } from "react-router";
import clsx from "clsx";
const styles = {
diff --git a/apps/site/app/components/Layout.tsx b/apps/site/app/components/Layout.tsx
index 54d9cab5..b78566f4 100644
--- a/apps/site/app/components/Layout.tsx
+++ b/apps/site/app/components/Layout.tsx
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from "react";
-import { Link, useLocation } from "@remix-run/react";
+import { Link, useLocation } from "react-router";
import clsx from "clsx";
import { Hero } from "~/components/Hero";
diff --git a/apps/site/app/components/MobileNavigation.tsx b/apps/site/app/components/MobileNavigation.tsx
index 2e043527..eed503fb 100644
--- a/apps/site/app/components/MobileNavigation.tsx
+++ b/apps/site/app/components/MobileNavigation.tsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { Link, useNavigation } from "@remix-run/react";
+import { Link, useNavigation } from "react-router";
import { Dialog } from "@headlessui/react";
import { Logomark } from "~/components/Logo";
diff --git a/apps/site/app/components/Navigation.tsx b/apps/site/app/components/Navigation.tsx
index 3cc14d5e..3beed018 100644
--- a/apps/site/app/components/Navigation.tsx
+++ b/apps/site/app/components/Navigation.tsx
@@ -1,4 +1,4 @@
-import { Link, useLocation } from "@remix-run/react";
+import { Link, useLocation } from "react-router";
import clsx from "clsx";
import { Manifest } from "~/docs.server";
diff --git a/apps/site/app/components/QuickLinks.jsx b/apps/site/app/components/QuickLinks.jsx
index 55858dfe..63533dc1 100644
--- a/apps/site/app/components/QuickLinks.jsx
+++ b/apps/site/app/components/QuickLinks.jsx
@@ -1,4 +1,4 @@
-import { Link } from "@remix-run/react";
+import { Link } from "react-router";
import { Icon } from "./Icon";
diff --git a/apps/site/app/components/Search.tsx b/apps/site/app/components/Search.tsx
index ac0956d8..b6b4aa2e 100644
--- a/apps/site/app/components/Search.tsx
+++ b/apps/site/app/components/Search.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState } from "react";
import { createPortal } from "react-dom";
-import { Link, useNavigate } from "@remix-run/react";
+import { Link, useNavigate } from "react-router";
import { DocSearchModal, useDocSearchKeyboardEvents } from "@docsearch/react";
function Hit({ hit, children }: { hit: any; children: any }) {
diff --git a/apps/site/app/entry.server.tsx b/apps/site/app/entry.server.tsx
new file mode 100644
index 00000000..89d682ad
--- /dev/null
+++ b/apps/site/app/entry.server.tsx
@@ -0,0 +1,51 @@
+/**
+ * By default, Remix will handle generating the HTTP Response for you.
+ * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
+ * For more information, see https://remix.run/file-conventions/entry.server
+ */
+
+import { isbot } from "isbot";
+import { renderToReadableStream } from "react-dom/server";
+import {
+ type AppLoadContext,
+ type EntryContext,
+ ServerRouter,
+} from "react-router";
+
+export default async function handleRequest(
+ request: Request,
+ responseStatusCode: number,
+ responseHeaders: Headers,
+ routerContext: EntryContext
+) {
+ let shellRendered = false;
+ const userAgent = request.headers.get("user-agent");
+
+ const body = await renderToReadableStream(
+ ,
+ {
+ onError(error: unknown) {
+ responseStatusCode = 500;
+ // Log streaming rendering errors from inside the shell. Don't log
+ // errors encountered during initial shell rendering since they'll
+ // reject and get logged in handleDocumentRequest.
+ if (shellRendered) {
+ console.error(error);
+ }
+ },
+ }
+ );
+ shellRendered = true;
+
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
+ if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
+ await body.allReady;
+ }
+
+ responseHeaders.set("Content-Type", "text/html");
+ return new Response(body, {
+ headers: responseHeaders,
+ status: responseStatusCode,
+ });
+}
diff --git a/apps/site/app/root.tsx b/apps/site/app/root.tsx
index 31dfb615..77f8ebc4 100644
--- a/apps/site/app/root.tsx
+++ b/apps/site/app/root.tsx
@@ -1,21 +1,19 @@
import {
- json,
type LinksFunction,
type MetaFunction,
- type LoaderFunctionArgs,
-} from "@remix-run/cloudflare";
-import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
-} from "@remix-run/react";
+} from "react-router";
import "@docsearch/css";
import "focus-visible";
import "./styles/tailwind.css";
+import type { Route } from "./routes/+types/_index";
+
export const meta: MetaFunction = () => [
{ title: "Superflare", "twitter:title": "Superflare" },
{ viewport: "width=device-width,initial-scale=1" },
@@ -45,14 +43,14 @@ export const links: LinksFunction = () => [
},
];
-export async function loader({ context: { cloudflare } }: LoaderFunctionArgs) {
- return json({
+export async function loader({ context: { cloudflare } }: Route.LoaderArgs) {
+ return {
ENV: {
DOCSEARCH_APP_ID: cloudflare.env.DOCSEARCH_APP_ID,
DOCSEARCH_API_KEY: cloudflare.env.DOCSEARCH_API_KEY,
DOCSEARCH_INDEX_NAME: cloudflare.env.DOCSEARCH_INDEX_NAME,
},
- });
+ };
}
const themeScript = `
diff --git a/apps/site/app/routes.ts b/apps/site/app/routes.ts
new file mode 100644
index 00000000..4c05936c
--- /dev/null
+++ b/apps/site/app/routes.ts
@@ -0,0 +1,4 @@
+import { type RouteConfig } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+
+export default flatRoutes() satisfies RouteConfig;
diff --git a/apps/site/app/routes/$.tsx b/apps/site/app/routes/$.tsx
index 412e2e37..e3dc734c 100644
--- a/apps/site/app/routes/$.tsx
+++ b/apps/site/app/routes/$.tsx
@@ -1,4 +1,4 @@
-import { MetaFunction } from "@remix-run/react/dist/routeModules";
+import { type MetaFunction } from "react-router";
import { loader as indexLoader } from "./_index";
@@ -7,9 +7,7 @@ export { default } from "./_index";
export const loader = indexLoader;
export const meta: MetaFunction = ({ data }) => [
- {
- title: data?.title ? `${data.title} - Superflare` : "Superflare",
- },
+ { title: data?.title ? `${data.title} - Superflare` : "Superflare" },
{
"twitter:title": data?.title ? `${data.title} - Superflare` : "Superflare",
},
diff --git a/apps/site/app/routes/_index.tsx b/apps/site/app/routes/_index.tsx
index 6be01456..2f3ce377 100644
--- a/apps/site/app/routes/_index.tsx
+++ b/apps/site/app/routes/_index.tsx
@@ -1,18 +1,16 @@
-import {
- json,
- type LoaderFunctionArgs,
- type MetaFunction,
-} from "@remix-run/cloudflare";
-import { useLoaderData } from "@remix-run/react";
+import { type MetaFunction } from "react-router";
+import { useLoaderData } from "react-router";
import { Layout } from "~/components/Layout";
import { getManifest, getMarkdownForPath, parseMarkdoc } from "~/docs.server";
import { renderMarkdoc } from "~/markdoc";
+import type { Route } from "./+types/_index";
+
export async function loader({
params,
context: { cloudflare },
-}: LoaderFunctionArgs) {
- const path = params["*"] ?? ("index" as string);
+}: Route.LoaderArgs) {
+ const path = params["*"] ?? "index";
const useGitHub = process.env.NODE_ENV === "production";
const markdown = await getMarkdownForPath(
@@ -34,7 +32,7 @@ export async function loader({
const { content, title, tableOfContents, description } =
parseMarkdoc(markdown);
- return json({ content, title, tableOfContents, manifest, description });
+ return { content, title, tableOfContents, manifest, description };
}
export const meta: MetaFunction = ({ data }) => [
diff --git a/apps/site/load-context.ts b/apps/site/load-context.ts
index 23889d2d..c8314b57 100644
--- a/apps/site/load-context.ts
+++ b/apps/site/load-context.ts
@@ -7,8 +7,10 @@ type Cloudflare = Omit, "dispose" | "caches"> & {
caches: CacheStorage;
};
-declare module "@remix-run/cloudflare" {
+declare module "react-router" {
interface AppLoadContext {
cloudflare: Cloudflare;
}
}
+
+export {}; // necessary for TS to treat this as a module
diff --git a/apps/site/package.json b/apps/site/package.json
index 67a71605..c21fb27a 100644
--- a/apps/site/package.json
+++ b/apps/site/package.json
@@ -4,14 +4,14 @@
"type": "module",
"private": true,
"scripts": {
- "build": "remix vite:build",
+ "build": "react-router build",
"deploy": "wrangler deploy",
- "dev:remix": "remix vite:dev",
+ "dev:remix": "react-router dev",
"dev:docs": "node ./scripts/serve-local-docs.mjs",
"dev": "concurrently \"npm:dev:*\"",
"start": "wrangler dev",
- "typegen": "wrangler types",
- "typecheck": "tsc"
+ "typegen": "wrangler types && react-router typegen",
+ "typecheck": "react-router typegen && tsc"
},
"browserslist": "defaults, not ie <= 11",
"dependencies": {
@@ -19,8 +19,8 @@
"@docsearch/react": "^3.8.0",
"@headlessui/react": "^2.1.2",
"@markdoc/markdoc": "0.4.0",
- "@remix-run/cloudflare": "^2.12.1",
- "@remix-run/react": "^2.12.1",
+ "@react-router/cloudflare": "^7.0.0",
+ "@react-router/fs-routes": "^7.0.0",
"@sindresorhus/slugify": "^2.1.1",
"@tailwindcss/typography": "^0.5.8",
"autoprefixer": "^10.4.19",
@@ -33,13 +33,13 @@
"prism-react-renderer": "^2.3.1",
"react": "18.2.0",
"react-dom": "18.2.0",
+ "react-router": "^7.0.0",
"tailwindcss": "^3.4.4",
"tiny-invariant": "^1.3.1"
},
"devDependencies": {
- "@cloudflare/workers-types": "^4.20241011.0",
- "@remix-run/dev": "^2.12.1",
- "@remix-run/eslint-config": "^2.12.1",
+ "@cloudflare/workers-types": "^4.20250109.0",
+ "@react-router/dev": "^7.0.0",
"@types/js-yaml": "^4.0.5",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
diff --git a/apps/site/react-router.config.ts b/apps/site/react-router.config.ts
new file mode 100644
index 00000000..04e55c93
--- /dev/null
+++ b/apps/site/react-router.config.ts
@@ -0,0 +1,3 @@
+import { type Config } from "@react-router/dev/config";
+
+export default { ssr: true } satisfies Config;
diff --git a/apps/site/tsconfig.json b/apps/site/tsconfig.json
index 42d66e8c..92137b02 100644
--- a/apps/site/tsconfig.json
+++ b/apps/site/tsconfig.json
@@ -1,8 +1,18 @@
{
- "include": ["worker-configuration.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": [
+ "worker-configuration.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".react-router/types/**/*"
+ ],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
- "types": ["@remix-run/cloudflare", "vite/client"],
+ "types": [
+ "@cloudflare/workers-types",
+ "@react-router/cloudflare",
+ "vite/client"
+ ],
+ "rootDirs": [".", "./.react-router/types"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
@@ -18,7 +28,7 @@
"~/*": ["./app/*"]
},
- // Remix takes care of building everything in `remix build`.
+ // React router takes care of building everything in `react-router build`.
"noEmit": true
}
}
diff --git a/apps/site/types/build.d.ts b/apps/site/types/build.d.ts
deleted file mode 100644
index 7744b692..00000000
--- a/apps/site/types/build.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { type ServerBuild } from "@remix-run/cloudflare";
-
-export const assets: ServerBuild["assets"];
-export const assetsBuildDirectory: ServerBuild["assetsBuildDirectory"];
-export const entry: ServerBuild["entry"];
-export const future: ServerBuild["future"];
-export const publicPath: ServerBuild["publicPath"];
-export const routes: ServerBuild["routes"];
diff --git a/apps/site/vite.config.ts b/apps/site/vite.config.ts
index eaa5f35a..c3611714 100644
--- a/apps/site/vite.config.ts
+++ b/apps/site/vite.config.ts
@@ -1,23 +1,10 @@
+import { reactRouter } from "@react-router/dev/vite";
+import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
import { defineConfig } from "vite";
-import {
- vitePlugin as remix,
- cloudflareDevProxyVitePlugin,
-} from "@remix-run/dev";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
- plugins: [
- cloudflareDevProxyVitePlugin(),
- remix({
- future: {
- v3_fetcherPersist: true,
- v3_lazyRouteDiscovery: true,
- v3_relativeSplatPath: true,
- v3_throwAbortReason: true,
- },
- }),
- tsconfigPaths(),
- ],
+ plugins: [cloudflareDevProxy(), reactRouter(), tsconfigPaths()],
ssr: {
noExternal: ["@docsearch/react", "@markdoc/markdoc"],
resolve: {
diff --git a/apps/site/worker-configuration.d.ts b/apps/site/worker-configuration.d.ts
index dbd8675b..ec78ae4b 100644
--- a/apps/site/worker-configuration.d.ts
+++ b/apps/site/worker-configuration.d.ts
@@ -4,4 +4,5 @@ interface Env {
DOCSEARCH_APP_ID: string;
DOCSEARCH_API_KEY: string;
DOCSEARCH_INDEX_NAME: string;
+ GITHUB_TOKEN: string;
}
diff --git a/apps/site/worker.ts b/apps/site/worker.ts
index 8d1620cf..fda4c088 100644
--- a/apps/site/worker.ts
+++ b/apps/site/worker.ts
@@ -1,10 +1,10 @@
-import { createRequestHandler, type ServerBuild } from "@remix-run/cloudflare";
+import { createRequestHandler, type ServerBuild } from "react-router";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore This file won’t exist if it hasn’t yet been built
import * as build from "./build/server"; // eslint-disable-line import/no-unresolved
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const handleRemixRequest = createRequestHandler(build as any as ServerBuild);
+const handleRequest = createRequestHandler(build as any as ServerBuild);
export default {
async fetch(request, env, ctx) {
@@ -15,7 +15,7 @@ export default {
// `getPlatformProxy` used during development via Remix's
// `cloudflareDevProxyVitePlugin`:
// https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy
- cf: request.cf,
+ cf: request.cf!,
ctx: {
passThroughOnException: ctx.passThroughOnException.bind(ctx),
waitUntil: ctx.waitUntil.bind(ctx),
@@ -24,7 +24,7 @@ export default {
env,
},
};
- return await handleRemixRequest(request, loadContext);
+ return await handleRequest(request, loadContext);
} catch (error) {
console.log(error);
return new Response("An unexpected error occurred", { status: 500 });
diff --git a/examples/remix-cms/.eslintrc.js b/examples/remix-cms/.eslintrc.js
index 2061cd22..d92655b4 100644
--- a/examples/remix-cms/.eslintrc.js
+++ b/examples/remix-cms/.eslintrc.js
@@ -1,4 +1,4 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
- extends: ["@remix-run/eslint-config", "@remix-run/eslint-config/node"],
+ extends: [],
};
diff --git a/examples/remix-cms/.gitignore b/examples/remix-cms/.gitignore
index d520faff..de346a68 100644
--- a/examples/remix-cms/.gitignore
+++ b/examples/remix-cms/.gitignore
@@ -7,3 +7,4 @@ node_modules
.env
/.wrangler
superflare.env.d.ts
+.react-router/
diff --git a/examples/remix-cms/app/components/admin/Button.tsx b/examples/remix-cms/app/components/admin/Button.tsx
index 1ec2de05..f59fd853 100644
--- a/examples/remix-cms/app/components/admin/Button.tsx
+++ b/examples/remix-cms/app/components/admin/Button.tsx
@@ -1,4 +1,4 @@
-import { Link } from "@remix-run/react";
+import { Link } from "react-router";
import clsx from "clsx";
export function Button({
diff --git a/examples/remix-cms/app/entry.client.tsx b/examples/remix-cms/app/entry.client.tsx
deleted file mode 100644
index 94d5dc0d..00000000
--- a/examples/remix-cms/app/entry.client.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * By default, Remix will handle hydrating your app on the client for you.
- * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
- * For more information, see https://remix.run/file-conventions/entry.client
- */
-
-import { RemixBrowser } from "@remix-run/react";
-import { startTransition, StrictMode } from "react";
-import { hydrateRoot } from "react-dom/client";
-
-startTransition(() => {
- hydrateRoot(
- document,
-
-
-
- );
-});
diff --git a/examples/remix-cms/app/entry.server.tsx b/examples/remix-cms/app/entry.server.tsx
index d3c0bbbd..3acd2d67 100644
--- a/examples/remix-cms/app/entry.server.tsx
+++ b/examples/remix-cms/app/entry.server.tsx
@@ -1,34 +1,41 @@
import { renderToReadableStream } from "react-dom/server";
-import { type EntryContext } from "@remix-run/cloudflare";
-import { RemixServer } from "@remix-run/react";
+import { type EntryContext, ServerRouter } from "react-router";
import { isbot } from "isbot";
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
- remixContext: EntryContext
+ routerContext: EntryContext
) {
+ let shellRendered = false;
+ const userAgent = request.headers.get("user-agent");
+
const body = await renderToReadableStream(
- ,
+ ,
{
onError(error: unknown) {
responseStatusCode = 500;
- // Log streaming rendering errors from inside the shell
- console.error(error);
+ // Log streaming rendering errors from inside the shell. Don't log
+ // errors encountered during initial shell rendering since they'll
+ // reject and get logged in handleDocumentRequest.
+ if (shellRendered) {
+ console.error(error);
+ }
},
- signal: request.signal,
}
);
+ shellRendered = true;
- if (isbot(request.headers.get("user-agent") || "")) {
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
+ if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
await body.allReady;
}
responseHeaders.set("Content-Type", "text/html");
-
return new Response(body, {
- status: responseStatusCode,
headers: responseHeaders,
+ status: responseStatusCode,
});
}
diff --git a/examples/remix-cms/app/models/Article.ts b/examples/remix-cms/app/models/Article.ts
index 943c80ed..74fdc7fa 100644
--- a/examples/remix-cms/app/models/Article.ts
+++ b/examples/remix-cms/app/models/Article.ts
@@ -2,7 +2,7 @@ import { Model } from "superflare";
import { User } from "./User";
export class Article extends Model {
- user!: User | Promise;
+ user!: User;
$user() {
return this.belongsTo(User);
}
diff --git a/examples/remix-cms/app/models/User.ts b/examples/remix-cms/app/models/User.ts
index 95a280e9..859ec91a 100644
--- a/examples/remix-cms/app/models/User.ts
+++ b/examples/remix-cms/app/models/User.ts
@@ -6,6 +6,7 @@ export class User extends Model {
return rest;
}
}
+
Model.register(User);
export interface User extends UserRow {}
diff --git a/examples/remix-cms/app/root.tsx b/examples/remix-cms/app/root.tsx
index 02259e80..82507d34 100644
--- a/examples/remix-cms/app/root.tsx
+++ b/examples/remix-cms/app/root.tsx
@@ -1,11 +1,12 @@
-import type { LinksFunction, MetaFunction } from "@remix-run/cloudflare";
import {
+ type LinksFunction,
+ type MetaFunction,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
-} from "@remix-run/react";
+} from "react-router";
import "./tailwind.css";
import "./styles/syntax.css";
diff --git a/examples/remix-cms/app/routes.ts b/examples/remix-cms/app/routes.ts
new file mode 100644
index 00000000..4c05936c
--- /dev/null
+++ b/examples/remix-cms/app/routes.ts
@@ -0,0 +1,4 @@
+import { type RouteConfig } from "@react-router/dev/routes";
+import { flatRoutes } from "@react-router/fs-routes";
+
+export default flatRoutes() satisfies RouteConfig;
diff --git a/examples/remix-cms/app/routes/index.tsx b/examples/remix-cms/app/routes/_index.tsx
similarity index 100%
rename from examples/remix-cms/app/routes/index.tsx
rename to examples/remix-cms/app/routes/_index.tsx
diff --git a/examples/remix-cms/app/routes/admin/index.tsx b/examples/remix-cms/app/routes/admin._index.tsx
similarity index 100%
rename from examples/remix-cms/app/routes/admin/index.tsx
rename to examples/remix-cms/app/routes/admin._index.tsx
diff --git a/examples/remix-cms/app/routes/admin/articles.$slug.tsx b/examples/remix-cms/app/routes/admin.articles.$slug._index.tsx
similarity index 78%
rename from examples/remix-cms/app/routes/admin/articles.$slug.tsx
rename to examples/remix-cms/app/routes/admin.articles.$slug._index.tsx
index f7732034..d5b7cea4 100644
--- a/examples/remix-cms/app/routes/admin/articles.$slug.tsx
+++ b/examples/remix-cms/app/routes/admin.articles.$slug._index.tsx
@@ -1,17 +1,18 @@
import { EyeIcon } from "@heroicons/react/24/outline";
-import { json, type LoaderFunctionArgs } from "@remix-run/cloudflare";
-import { useLoaderData, useRevalidator } from "@remix-run/react";
+import { useLoaderData, useRevalidator } from "react-router";
import invariant from "tiny-invariant";
import { Button, SecondaryButton } from "~/components/admin/Button";
import { Page } from "~/components/admin/Page";
import { SayHelloJob } from "~/jobs/SayHelloJob";
import { Article } from "~/models/Article";
import { useChannel } from "~/utils/use-channel";
-import { ArticleForm } from "./components/article-form";
+import { ArticleForm } from "./admin/components/article-form";
-export { action } from "./components/article-form";
+import type { Route } from "./+types/admin.articles.$slug._index";
-export async function loader({ params }: LoaderFunctionArgs) {
+export { action } from "./admin/components/article-form";
+
+export async function loader({ params }: Route.LoaderArgs) {
const { slug } = params;
invariant(typeof slug === "string", "Missing slug");
@@ -21,10 +22,10 @@ export async function loader({ params }: LoaderFunctionArgs) {
if (!article) {
throw new Response("Not found", { status: 404 });
}
-
+ console.log(article.user.name);
SayHelloJob.dispatch(article);
- return json({ article });
+ return { article };
}
export default function NewArticle() {
diff --git a/examples/remix-cms/app/routes/admin/articles.$slug.preview.tsx b/examples/remix-cms/app/routes/admin.articles.$slug.preview.tsx
similarity index 84%
rename from examples/remix-cms/app/routes/admin/articles.$slug.preview.tsx
rename to examples/remix-cms/app/routes/admin.articles.$slug.preview.tsx
index b4edd183..b7fa53b4 100644
--- a/examples/remix-cms/app/routes/admin/articles.$slug.preview.tsx
+++ b/examples/remix-cms/app/routes/admin.articles.$slug.preview.tsx
@@ -1,6 +1,5 @@
import { PencilSquareIcon } from "@heroicons/react/24/outline";
-import { json, type LoaderFunctionArgs } from "@remix-run/cloudflare";
-import { useLoaderData } from "@remix-run/react";
+import { useLoaderData } from "react-router";
import invariant from "tiny-invariant";
import { SecondaryButton } from "~/components/admin/Button";
@@ -8,7 +7,9 @@ import { Page } from "~/components/admin/Page";
import { Article } from "~/models/Article";
import { convertToHtml } from "~/utils/markdown.server";
-export async function loader({ params }: LoaderFunctionArgs) {
+import type { Route } from "./+types/admin.articles.$slug.preview";
+
+export async function loader({ params }: Route.LoaderArgs) {
const { slug } = params;
invariant(typeof slug === "string", "Missing slug");
@@ -19,10 +20,10 @@ export async function loader({ params }: LoaderFunctionArgs) {
throw new Response("Not found", { status: 404 });
}
- return json({
+ return {
article,
html: await convertToHtml(article.content ?? ""),
- });
+ };
}
export default function NewArticle() {
diff --git a/examples/remix-cms/app/routes/admin/articles.tsx b/examples/remix-cms/app/routes/admin.articles._index.tsx
similarity index 96%
rename from examples/remix-cms/app/routes/admin/articles.tsx
rename to examples/remix-cms/app/routes/admin.articles._index.tsx
index 91c6ae79..f6db27d8 100644
--- a/examples/remix-cms/app/routes/admin/articles.tsx
+++ b/examples/remix-cms/app/routes/admin.articles._index.tsx
@@ -1,5 +1,4 @@
-import { json } from "@remix-run/cloudflare";
-import { Link, useLoaderData } from "@remix-run/react";
+import { Link, useLoaderData } from "react-router";
import { Button } from "~/components/admin/Button";
import { Page } from "~/components/admin/Page";
import { Article } from "~/models/Article";
@@ -8,7 +7,7 @@ import { useChannel } from "~/utils/use-channel";
export async function loader() {
const articles = await Article.with("user").orderBy("createdAt", "desc");
- return json({ articles });
+ return { articles };
}
export default function Articles() {
diff --git a/examples/remix-cms/app/routes/admin/articles.new.tsx b/examples/remix-cms/app/routes/admin.articles.new.tsx
similarity index 74%
rename from examples/remix-cms/app/routes/admin/articles.new.tsx
rename to examples/remix-cms/app/routes/admin.articles.new.tsx
index 9757d7a7..d8cd1608 100644
--- a/examples/remix-cms/app/routes/admin/articles.new.tsx
+++ b/examples/remix-cms/app/routes/admin.articles.new.tsx
@@ -1,8 +1,8 @@
import { Button } from "~/components/admin/Button";
import { Page } from "~/components/admin/Page";
-import { ArticleForm } from "./components/article-form";
+import { ArticleForm } from "./admin/components/article-form";
-export { action } from "./components/article-form";
+export { action } from "./admin/components/article-form";
export default function NewArticle() {
return (
diff --git a/examples/remix-cms/app/routes/admin/profile.tsx b/examples/remix-cms/app/routes/admin.profile.tsx
similarity index 80%
rename from examples/remix-cms/app/routes/admin/profile.tsx
rename to examples/remix-cms/app/routes/admin.profile.tsx
index c902e6fe..c73da397 100644
--- a/examples/remix-cms/app/routes/admin/profile.tsx
+++ b/examples/remix-cms/app/routes/admin.profile.tsx
@@ -1,6 +1,6 @@
-import { Form } from "@remix-run/react";
+import { Form } from "react-router";
import { Page } from "~/components/admin/Page";
-import { useAdmin } from "../auth/hooks";
+import { useAdmin } from "./auth/hooks";
export default function Profile() {
const adminData = useAdmin();
diff --git a/examples/remix-cms/app/routes/admin.tsx b/examples/remix-cms/app/routes/admin.tsx
index 593cf6d8..0c010920 100644
--- a/examples/remix-cms/app/routes/admin.tsx
+++ b/examples/remix-cms/app/routes/admin.tsx
@@ -12,19 +12,18 @@ import {
XMarkIcon,
} from "@heroicons/react/24/outline";
import clsx from "clsx";
-import { Link, NavLink, Outlet, useLoaderData } from "@remix-run/react";
+import { Link, NavLink, Outlet, redirect, useLoaderData } from "react-router";
import { Toast } from "~/components/Toast";
-import { json, type LoaderFunctionArgs, redirect } from "@remix-run/cloudflare";
import { User } from "~/models/User";
+import type { Route } from "./+types/admin";
+
const navigation = [
{ name: "Dashboard", href: "/admin", icon: HomeIcon, end: true },
{ name: "Articles", href: "./articles", icon: FolderIcon },
];
-export async function loader({
- context: { auth, session },
-}: LoaderFunctionArgs) {
+export async function loader({ context: { auth, session } }: Route.LoaderArgs) {
if (!(await auth.check(User))) {
return redirect("/auth/login");
}
@@ -33,10 +32,7 @@ export async function loader({
const user = await auth.user(User);
- return json({
- flash,
- user,
- });
+ return { flash, user };
}
export default function AdminLayout() {
diff --git a/examples/remix-cms/app/routes/admin/upload.$.ts b/examples/remix-cms/app/routes/admin.upload.$.ts
similarity index 59%
rename from examples/remix-cms/app/routes/admin/upload.$.ts
rename to examples/remix-cms/app/routes/admin.upload.$.ts
index 66a572e1..ce872f47 100644
--- a/examples/remix-cms/app/routes/admin/upload.$.ts
+++ b/examples/remix-cms/app/routes/admin.upload.$.ts
@@ -1,7 +1,8 @@
-import { json, type ActionFunctionArgs } from "@remix-run/cloudflare";
import { parseMultipartFormData, storage } from "superflare";
-export async function action({ request }: ActionFunctionArgs) {
+import type { Route } from "./+types/admin.upload.$";
+
+export async function action({ request }: Route.ActionArgs) {
const formData = await parseMultipartFormData(
request,
async ({ stream, filename }) => {
@@ -13,7 +14,5 @@ export async function action({ request }: ActionFunctionArgs) {
}
);
- return json({
- url: storage().url(formData.get("file") as string),
- });
+ return { url: storage().url(formData.get("file") as string) };
}
diff --git a/examples/remix-cms/app/routes/admin/components/article-form.tsx b/examples/remix-cms/app/routes/admin/components/article-form.tsx
index 19fde31d..3e077d85 100644
--- a/examples/remix-cms/app/routes/admin/components/article-form.tsx
+++ b/examples/remix-cms/app/routes/admin/components/article-form.tsx
@@ -1,10 +1,4 @@
-import {
- json,
- redirect,
- type SerializeFrom,
- type ActionFunctionArgs,
-} from "@remix-run/cloudflare";
-import { Form, useActionData } from "@remix-run/react";
+import { Form, redirect, useActionData } from "react-router";
import { Article } from "~/models/Article";
import invariant from "tiny-invariant";
import { FormField } from "~/components/Form";
@@ -12,6 +6,8 @@ import MarkdownComposer from "~/components/admin/MarkdownComposer";
import { User } from "~/models/User";
import { ArticleUpdated } from "~/events/ArticleUpdated";
+import type { Route } from "../../+types/admin.articles.$slug._index";
+
interface ActionData {
title: string | null;
content: string | null;
@@ -29,12 +25,12 @@ const enum Intent {
Update = "update",
}
-const badResponse = (data: ActionData) => json(data, { status: 422 });
+const badResponse = (data: ActionData) => Response.json(data, { status: 422 });
export async function action({
request,
context: { auth, session },
-}: ActionFunctionArgs) {
+}: Route.ActionArgs) {
const body = new URLSearchParams(await request.text());
const title = body.get("title");
const content = body.get("content");
@@ -110,7 +106,7 @@ export function ArticleForm({
article,
id,
}: {
- article?: SerializeFrom;
+ article?: Article;
id?: string;
}) {
const actionData = useActionData();
diff --git a/examples/remix-cms/app/routes/auth/login.tsx b/examples/remix-cms/app/routes/auth.login.tsx
similarity index 82%
rename from examples/remix-cms/app/routes/auth/login.tsx
rename to examples/remix-cms/app/routes/auth.login.tsx
index bb3b5124..8db8b42a 100644
--- a/examples/remix-cms/app/routes/auth/login.tsx
+++ b/examples/remix-cms/app/routes/auth.login.tsx
@@ -1,13 +1,11 @@
-import { Form, Link, useActionData } from "@remix-run/react";
-import { json, redirect, type ActionFunctionArgs } from "@remix-run/cloudflare";
+import { Form, Link, redirect, useActionData } from "react-router";
import { Button } from "~/components/admin/Button";
import { FormField } from "~/components/Form";
import { User } from "~/models/User";
-export async function action({
- request,
- context: { auth },
-}: ActionFunctionArgs) {
+import type { Route } from "./+types/auth.login";
+
+export async function action({ request, context: { auth } }: Route.ActionArgs) {
if (await auth.check(User)) {
return redirect("/admin");
}
@@ -26,11 +24,11 @@ export async function action({
return redirect("/admin");
}
- return json({ error: "Invalid credentials" }, { status: 400 });
+ return Response.json({ error: "Invalid credentials" }, { status: 400 });
}
export default function Login() {
- const actionData = useActionData();
+ const actionData = useActionData<{ error: string }>();
return (
<>
diff --git a/examples/remix-cms/app/routes/auth.logout.ts b/examples/remix-cms/app/routes/auth.logout.ts
new file mode 100644
index 00000000..3877f409
--- /dev/null
+++ b/examples/remix-cms/app/routes/auth.logout.ts
@@ -0,0 +1,9 @@
+import { redirect } from "react-router";
+
+import type { Route } from "./+types/auth.logout";
+
+export async function action({ context: { auth } }: Route.ActionArgs) {
+ auth.logout();
+
+ return redirect("/");
+}
diff --git a/examples/remix-cms/app/routes/auth/register.tsx b/examples/remix-cms/app/routes/auth.register.tsx
similarity index 81%
rename from examples/remix-cms/app/routes/auth/register.tsx
rename to examples/remix-cms/app/routes/auth.register.tsx
index 3ccc8ebc..fa949eed 100644
--- a/examples/remix-cms/app/routes/auth/register.tsx
+++ b/examples/remix-cms/app/routes/auth.register.tsx
@@ -1,14 +1,12 @@
-import { Form, Link, useActionData } from "@remix-run/react";
-import { json, redirect, type ActionFunctionArgs } from "@remix-run/cloudflare";
+import { Form, Link, redirect, useActionData } from "react-router";
import { Button } from "~/components/admin/Button";
import { FormField } from "~/components/Form";
import { User } from "~/models/User";
import { hash } from "superflare";
-export async function action({
- request,
- context: { auth },
-}: ActionFunctionArgs) {
+import type { Route } from "./+types/auth.register";
+
+export async function action({ request, context: { auth } }: Route.ActionArgs) {
if (await auth.check(User)) {
return redirect("/admin");
}
@@ -19,7 +17,7 @@ export async function action({
const name = formData.get("name") as string;
if (await User.where("email", email).count()) {
- return json({ error: "Email already exists" }, { status: 400 });
+ return Response.json({ error: "Email already exists" }, { status: 400 });
}
const user = await User.create({
@@ -34,7 +32,7 @@ export async function action({
}
export default function Register() {
- const actionData = useActionData();
+ const actionData = useActionData<{ error: string }>();
return (