The modern web would be different without rich client-side applications supported by powerful frameworks: React, Angular, Vue, Lit, and many others. These frameworks rely on client-side JavaScript, which is their core. However, there are other approaches to rendering. One of them (quite old, by the way) is server-side rendering entirely without JavaScript. Letβs find out if this is a good idea and how Remix can help us with it?
VSCode
, but also cloud IDEs such as codesandbox (other IDEs are also ok)Software Engineer, Netherlands
My primary interest is self development and craftsmanship. I enjoy exploring technologies, coding open source and enterprise projects, teaching, speaking and writing about programming - JavaScript, Node.js, TypeScript, Go, Java, Docker, Kubernetes, JSON Schema, DevOps, Web Components, Algorithms π β½οΈ π§βπ» π§
Software Engineer, Netherlands
JavaScript developer with full-stack experience and frontend passion. He runs a small development agency codeville.agency and likes to talk about technologies they use: React, Remix and Serverless.
a modern full stack web framework
import { json } from "@remix-run/node"; // or cloudflare/deno
export async function loader() {
const res = await fetch("https://api.github.com/gists");
const gists = await res.json();
return json(
gists.map((gist) => ({
description: gist.description,
url: gist.html_url,
files: Object.keys(gist.files),
owner: gist.owner.login,
}))
);
}
export default function Gists() {
const gists = useLoaderData<typeof loader>();
return (
<ul>
{gists.map((gist) => (
<li key={gist.id}>
<a href={gist.url}>
{gist.description}, {gist.owner}
</a>
<ul>
{gist.files.map((key) => (
<li key={key}>{key}</li>
))}
</ul>
</li>
))}
</ul>
);
}
a compiler for React Router
compiler - server side and client side app, alongside with manifest meta information
server
loader, action & default component
// https://github.com/remix-run/examples/blob/main/_official-blog-tutorial/app/routes/posts/index.tsx
import { json } from "@remix-run/node"
import { Link, useLoaderData } from "@remix-run/react"
import { getPosts } from "~/models/post.server"
export const loader = async () => json({ posts: await getPosts() })
export default function Posts() {
const { posts } = useLoaderData<typeof loader>()
return (
<main>
<h1>Posts</h1>
<Link to="admin" className="text-red-600 underline">Admin</Link>
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link to={post.slug} className="text-blue-600 underline">{post.title}</Link>
</li>
))}
</ul>
</main>
)
}
import { createPost } from "~/models/post.server"
export const action = async ({ request }: ActionArgs) => {
const formData = await request.formData()
const title = formData.get("title")
if (!title) return json({ error: "Title is required" })
await createPost({ title })
return redirect("/posts/admin")
};
export default function NewPost() {
const transition = useTransition()
const isCreating = Boolean(transition.submission)
return (
<Form method="post">
<label>
Post Title:{" "}<input type="text" name="title" />
</label>
<button type="submit" disabled={isCreating}>
{isCreating ? "Creating..." : "Create Post"}
</button>
</Form>
);
}
app/
βββ routes/
β βββ about.tsx // route is based on the static path
β βββ blog/ // route has an additional "/blog" segment in the URL
β β βββ $postId.tsx // dynamic params
β β βββ categories.tsx // static segments
β β βββ index.tsx // index of the "/blog" directory
β βββ $.tsx // splat route (catch-all routes)
β βββ blog.authors.tsx // dot delimiter
β βββ blog.tsx // layout for a regular route
β βββ index.tsx
βββ root.tsx
// app/routes/posts/index.tsx
var posts_exports = {};
__export(posts_exports, {
default: () => Posts,
loader: () => loader5
});
var import_node7 = require("@remix-run/node"),
import_react7 = require("@remix-run/react");
var import_jsx_dev_runtime7 = require("react/jsx-dev-runtime"),
loader5 = async () => (0, import_node7.json)({ posts: await getPosts() });
function Posts() {
let { posts } = (0, import_react7.useLoaderData)();
return /* @__PURE__ */ (0, import_jsx_dev_runtime7.jsxDEV)("main", { children: [
/* @__PURE__ */ (0, import_jsx_dev_runtime7.jsxDEV)("h1", { children: "Posts" }, void 0, !1, {
fileName: "app/routes/posts/index.tsx"
}, this),
/* @__PURE__ */ (0, import_jsx_dev_runtime7.jsxDEV)(import_react7.Link, { to: "admin", ...
}, this),
/* @__PURE__ */ (0, import_jsx_dev_runtime7.jsxDEV)("ul", { children: posts.map((post) => ...
}, this) }, post.slug, !1, {
fileName: "app/routes/posts/index.tsx"
...
}
DIY Letβs do Remix today with own hands!
import { useParams } from "react-router";
type useQueryType = { <T>(x: string): { data: T | null } };
const useQuery: useQueryType = (type) => ({ data: null });
type SalesType = { id: number; overdue: string; dueSoon: string };
type InvoiceType = { id: number; user: string; value: string; details: string };
// URL: /sales/invoices/1
export const InvoicesPage = () => {
return (
<div>
<Invoices />
</div>
);
};
const Invoices = () => {
const { data: invoices } = useQuery<InvoiceType[]>("invoices");
if (!invoices) return null;
const { invoiceId } = useParams();
return (
<>
<ul>
{invoices.map((invoice: InvoiceType) => (
<li key={invoice.id}>
<a href={`/invoices/${invoice.id}`}>{invoice.user} {invoice.value}</a>
</li>
))}
</ul>
{invoiceId && <Invoice id={Number(invoiceId)} />}
</>
);
};
type InvoiceProps = { id: number };
const Invoice = ({ id }: InvoiceProps) => {
const { data: invoice } = useQuery<InvoiceType>(`invoices/${id}`);
if (!invoice) return null;
return (
<div>
{invoice.user}: {invoice.details}
</div>
);
};
Please share your feedback on the workshop. Thank you and have a great coding!
If you like the workshop, you can become our patron, yay! π