Is Remix your next framework?




Michael Jackson
Ryan Florence
Kent C Dodds
Remix is driven by community leaders

Reaching the goal
〞
Perhaps JavaScript should not be the starting point for the developer, but should be thought of as an extension.

Works without JavaScript

CSR vs SSR vs SSG vs ISR
Where to render

Client Side Rendering (CSR)
Server Side Rendering (SSR)


Server Side Generation (SSG/ISR)

Remix

Remix
Using what we know
It's just React

Without
the hard parts

React without
the hard parts


Learn Remix, Accidentally Learn the Web

Full Stack


Remix vs NextJS
Remix is all about
- Developer experience (DX)
- Performance & speed (UX)
- Using web standard
Routing

app
├── root.jsx
└── routes
├── accounts.jsx
├── dashboard.jsx
├── expenses.jsx
├── index.jsx
├── reports.jsx
├── sales
│ ├── customers.jsx
│ ├── deposits.jsx
│ ├── index.jsx
│ ├── invoices
│ │ ├── $invoiceId.jsx
│ │ └── index.jsx
│ ├── invoices.jsx
│ └── subscriptions.jsx
└── sales.jsx
Defining routes
/sales/invoices/102000
- root.jsx
- routes/sales.jsx
- routes/sales/invoices.jsx
- routes/sales/invoices/$invoiceId.jsx<Root>
<Sales>
<Invoices>
<InvoiceId />
</Invoices>
</Sales>
</Root>Data Loading

Time to First Byte (TTFB)
First Contentful Paint (FCP)
Cumulative Layout Shift (CLS)
Largest Contentful Paint (LCP)
Time To Interactive (TTI)





How can we describe performance

Rendering of a standard SPA

Parallel data fetching
Server side rendering

Server side rendering with caching

import type { LoaderFunction } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";
import { db } from "~/db.server";
export const loader: LoaderFunction = async ({ params }) => {
return json(
await db.product.findMany({
where: { categoryId: params.categoryId },
})
);
};
export default function ProductCategory() {
const products = useLoaderData();
return (
<div>
<p>{products.length} Products</p>
{/* ... */}
</div>
);
}Route with data fetching
FORM ACTIONs

function MyForm() {
const [inputs, setInputs] = useState({});
const handleChange = (event) => {
const name = event.target.name;
const value = event.target.value;
setInputs(values => ({...values, [name]: value}))
}
const handleSubmit = (event) => {
event.preventDefault();
console.log(inputs);
};
return (
<form onSubmit={handleSubmit}>
<label>Enter your name:
<input type="text"
value={inputs.username || ""}
onChange={handleChange}
/>
</label>
</form>
)
}React Form


The power of Remix
GET /users
GET /users?name=peter
import type { LoaderArgs } from "@remix-run/node"; // or cloudflare/deno
import { redirect } from "@remix-run/node"; // or cloudflare/deno
export const loader = async ({ request }: LoaderArgs) => {
const url = new URL(request.url);
const search = new URLSearchParams(url.search);
const query = search.get('query') || '';
const data = [....];
return json({ query, data });
};
export default function Users() {
const { data, query } = useLoaderData();
return (
<h1>Search page</h1>
<form method='get'>
<input type='text' name='query' defaultValue={query}/>
</form>
)
}Filter form (get)
import type { ActionArgs } from "@remix-run/node"; // or cloudflare/deno
import { redirect } from "@remix-run/node"; // or cloudflare/deno
export const action = async ({ request }: ActionArgs) => {
const formData = await request.formData();
const user = await db.save.user(formData);
return redirect(`/users/${user.id}`);
};
export default function NewUser() {
return (
<form method="post" action="/search">
<input name="name" type="text" />
<input name="email" type="email" />
<button type="submit">Submit</button>
</form>
)
}Mutation form (post)
// Zod validation schema
export const formSchema = z.object({
name: z.string(),
email: z.string().email().optional()
});
export const action = async ({ request }) => {
const formPayload = Object.fromEntries(await request.formData());
try {
const formValues = formSchema.parse(formPayload);
const user = await db.save.user(formValues);
return redirect(`/users/${user.id}`);
} catch (error) {
return {
formPayload,
errors: (error as ZodError).flatten().fieldErrors,
};
}
};
Form validation - action
export default function NewUser() {
const actionData = useActionData();
return (
<form method="get" action="/search">
<input name="name" type="text" />
{actionData?.errors.name && (
<span className="text-red-600">{actionData?.errors.name[0]}</span>
)}
<input name=email" type="email" />
{actionData?.email.name && (
<span className="text-red-600">{actionData?.errors.email[0]}</span>
)}
<button type="submit">Submit</button>
</form>
)
}Form validation - handle errors
ERROR Handling

// root.aspx
export function ErrorBoundary({ error }) {
return (
<html>
<head>
<title>Oh no!</title>
<Meta />
<Links />
</head>
<body className='m-4'>
<h1 className='text-2xl'>Something went wrong!</h1>
<p>{error.message}</p>
<Scripts />
</body>
</html>
);
}Global Error handling
// my route component
export default () => {
return (
<div>...</div>
)
}
// Error Boundery Component
export function ErrorBoundary({ error }) {
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<p>The stack trace is:</p>
<pre>{error.stack}</pre>
</div>
);
}Route Error handling
// We can throw a 404 error
export const loader = async ({ params }: LoaderArgs) => {
const id = params.id;
const category = await getDb().categories.findOne({ _id: new SafeObjectId(id) });
if (!category) {
throw json({ message: 'Resources not found' }, 404);
}
return json({ category });
};Catch Error (http status errors)
// Status Catch Boundery
export function CatchBoundary() {
const { data, status } = useCatch();
return (
<div>
<p>Resources not found [{status} - {data.message}]</p>
</div>
);
}When to use


Software Spectrum

So less code
faster UX
better DX
Is Remix
your next
framework?

Want to learn more?

Is Remix your next framework?
By Peter Cosemans
Is Remix your next framework?
- 28
