···11-/**
22- * By default, Remix will handle hydrating your app on the client for you.
33- * 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` ✨
44- * For more information, see https://remix.run/file-conventions/entry.client
55- */
66-77-import { HydratedRouter } from "react-router/dom";
88-import { startTransition, StrictMode } from "react";
99-import { hydrateRoot } from "react-dom/client";
1010-1111-startTransition(() => {
1212- hydrateRoot(
1313- document,
1414- <StrictMode>
1515- <HydratedRouter />
1616- </StrictMode>
1717- );
1818-});
-56
src/app/entry.server.tsx
···11-/**
22- * By default, Remix will handle generating the HTTP Response for you.
33- * 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` ✨
44- * For more information, see https://remix.run/file-conventions/entry.server
55- */
66-77-import type { AppLoadContext, EntryContext } from "react-router";
88-import { ServerRouter } from "react-router";
99-import { isbot } from "isbot";
1010-import { renderToReadableStream } from "react-dom/server";
1111-1212-const ABORT_DELAY = 5000;
1313-1414-export default async function handleRequest(
1515- request: Request,
1616- responseStatusCode: number,
1717- responseHeaders: Headers,
1818- reactRouterContext: EntryContext,
1919- // This is ignored so we can keep it in the template for visibility. Feel
2020- // free to delete this parameter in your app if you're not using it!
2121- // eslint-disable-next-line @typescript-eslint/no-unused-vars
2222- loadContext: AppLoadContext
2323-) {
2424- const controller = new AbortController();
2525- const timeoutId = setTimeout(() => controller.abort(), ABORT_DELAY);
2626-2727- const body = await renderToReadableStream(
2828- <ServerRouter
2929- context={reactRouterContext}
3030- url={request.url}
3131- abortDelay={ABORT_DELAY}
3232- />,
3333- {
3434- signal: controller.signal,
3535- onError(error: unknown) {
3636- if (!controller.signal.aborted) {
3737- // Log streaming rendering errors from inside the shell
3838- console.error(error);
3939- }
4040- responseStatusCode = 500;
4141- },
4242- }
4343- );
4444-4545- body.allReady.then(() => clearTimeout(timeoutId));
4646-4747- if (isbot(request.headers.get("user-agent") || "")) {
4848- await body.allReady;
4949- }
5050-5151- responseHeaders.set("Content-Type", "text/html");
5252- return new Response(body, {
5353- headers: responseHeaders,
5454- status: responseStatusCode,
5555- });
5656-}
···11-import { type RouteConfig } from "@react-router/dev/routes";
22-import { flatRoutes } from "@react-router/fs-routes";
33-44-export default flatRoutes() satisfies RouteConfig;
-63
src/app/routes/_index.tsx
···11-import type { MetaFunction } from "react-router";
22-33-export const meta: MetaFunction = () => {
44- return [
55- { title: "hayden@web ~" },
66- { name: "description", content: "The blog of a lowly DevOps Engineer from the UK. Federated on atproto!" },
77- ];
88-};
99-1010-export default () => {
1111- return (
1212- <>
1313- <div className="prose max-w-2xl mx-auto">
1414- <p>
1515- I love working with all things DevOps, from automation and software
1616- engineering to full-on platform engineering. I’m a huge advocate for making
1717- things self-service and as reproducible as possible (hence my love/hate
1818- relationship with <a href="https://nixos.org">Nix</a>).
1919- </p>
2020-2121- <p>
2222- Recently, I’ve been learning how to build developer-focal automation,
2323- building up understanding of how to view both sides of the infrastructure
2424- coin; platform administration and developers.
2525- </p>
2626-2727- <p>
2828- I think pineapple on pizza is a crime, I listen to everything from rock and
2929- indie to jungle and breakcore, and I’m a huge fan of virtual reality.
3030- </p>
3131-3232- <p>
3333- I’m based in Sheffield, in the UK, and I’m always open to having
3434- conversations with people about any topic you think I’d find interesting.
3535- </p>
3636-3737- <p>
3838- <span className="line-through">also <a href="https://nohello.net">nohello.net</a> pls thx</span>
3939- </p>
4040- </div>
4141-4242- <div className="max-w-sm w-full mx-auto grid md:grid-cols-2 gap-4">
4343- <div className="flex flex-col gap-2">
4444- <span className="font-bold text-lg">On the web</span>
4545- <ul className="list-disc list-inside">
4646- <li><a className="text-pink underline" href="https://bsky.app/profile/hayden.moe">Bluesky</a></li>
4747- <li><a className="text-pink underline" href="https://discord.gg/vmuNsEuUyW">Discord</a></li>
4848- <li><a className="text-pink underline" href="https://github.com/hbjydev">GitHub</a></li>
4949- <li><a className="text-pink underline" href="https://twitch.tv/hayden_dev">Twitch</a></li>
5050- <li><a className="text-pink underline" href="mailto:hayden@hayden.moe">Email</a></li>
5151- </ul>
5252- </div>
5353- <div className="flex flex-col gap-2">
5454- <span className="font-bold text-lg">On this site</span>
5555- <ul className="list-disc list-inside">
5656- <li><a className="text-pink underline" href="/posts">Blog</a></li>
5757- <li><a className="text-pink underline" href="/ring">Webring</a></li>
5858- </ul>
5959- </div>
6060- </div>
6161- </>
6262- );
6363-}
-79
src/app/routes/posts.$rkey.tsx
···11-import { LoaderFunctionArgs, MetaFunction } from 'react-router';
22-import { useLoaderData } from 'react-router';
33-import { getPost } from 'src/atproto/getPost';
44-import { FormattedDate } from '../components/formatted-date';
55-import Markdown from 'react-markdown';
66-77-export const meta: MetaFunction<typeof loader> = ({ data }) => {
88- if (data) {
99- const desc = data.post.content?.slice(0, 100).trimEnd();
1010- const url = `https://hayden.moe/${data.post.rkey}`;
1111-1212- return [
1313- // HTML Meta
1414- { title: data.post.title },
1515- { name: 'description', content: desc },
1616-1717- // OG Meta
1818- { property: 'og:url', content: url },
1919- { property: 'og:type', content: 'website' },
2020- { property: 'og:title', content: data.post.title },
2121- { property: 'og:description', content: desc },
2222-2323- // Twitter Meta
2424- { name: 'twitter:card', content: 'summary_large_image' },
2525- { property: 'twitter:domain', content: 'hayden.moe' },
2626- { property: 'twitter:url', content: url },
2727- { name: 'twitter:title', content: data.post.title },
2828- { name: 'twitter:description', content: desc },
2929- ];
3030- } else {
3131- return [
3232- { title: 'Not found' },
3333- { name: 'description', content: "This post doesn't exist!" },
3434- ];
3535- }
3636-}
3737-3838-export const loader = async ({ params, context }: LoaderFunctionArgs) => {
3939- const { rkey } = params;
4040- try {
4141- const post = await getPost(context, rkey!);
4242- return { post };
4343- } catch(e) {
4444- throw new Response(null, {
4545- status: 404,
4646- statusText: 'Post not found.',
4747- });
4848- }
4949-};
5050-5151-export default () => {
5252- const { post } = useLoaderData<typeof loader>();
5353-5454- return (
5555- <>
5656- <header className="max-w-2xl mx-auto w-full">
5757- <h1 className="font-bold before-hash-1">{post.title}</h1>
5858- <span className="text-base03">
5959- <FormattedDate date={new Date(Date.parse(post.createdAt))} />
6060- </span>
6161- </header>
6262-6363- <hr />
6464-6565- <Markdown className="prose max-w-2xl mx-auto pb-5">
6666- {post.content}
6767- </Markdown>
6868-6969- <hr />
7070-7171- <p className="max-w-2xl mx-auto pb-5 prose">
7272- Thanks for reading along, I hope you enjoyed this post.
7373- If you did, maybe consider following me on <a href="https://bsky.app/profile/hayden.moe">Bluesky</a>,
7474- and if you're feeling generous, maybe consider <a href="https://ko-fi.com/haydenuwu">buying me a coffee</a>.
7575- I'm trying to write more this year, so I'll see you in the next post. 👋
7676- </p>
7777- </>
7878- );
7979-}
···11+---
22+import Shell from '../layouts/shell.astro';
33+---
44+55+<Shell>
66+ <div class="prose max-w-2xl mx-auto">
77+ <p>
88+ I love working with all things DevOps, from automation and software
99+ engineering to full-on platform engineering. I’m a huge advocate for making
1010+ things self-service and as reproducible as possible (hence my love/hate
1111+ relationship with <a href="https://nixos.org">Nix</a>).
1212+ </p>
1313+1414+ <p>
1515+ Recently, I’ve been learning how to build developer-focal automation,
1616+ building up understanding of how to view both sides of the infrastructure
1717+ coin; platform administration and developers.
1818+ </p>
1919+2020+ <p>
2121+ I think pineapple on pizza is a crime, I listen to everything from rock and
2222+ indie to jungle and breakcore, and I’m a huge fan of virtual reality.
2323+ </p>
2424+2525+ <p>
2626+ I’m based in Sheffield, in the UK, and I’m always open to having
2727+ conversations with people about any topic you think I’d find interesting.
2828+ </p>
2929+3030+ <p>
3131+ <span class="line-through">also <a href="https://nohello.net">nohello.net</a> pls thx</span>
3232+ </p>
3333+ </div>
3434+3535+ <div class="max-w-sm w-full mx-auto grid md:grid-cols-2 gap-4">
3636+ <div class="flex flex-col gap-2">
3737+ <span class="font-bold text-lg">On the web</span>
3838+ <ul class="list-disc list-inside">
3939+ <li><a class="text-pink underline" href="https://bsky.app/profile/hayden.moe">Bluesky</a></li>
4040+ <li><a class="text-pink underline" href="https://discord.gg/vmuNsEuUyW">Discord</a></li>
4141+ <li><a class="text-pink underline" href="https://github.com/hbjydev">GitHub</a></li>
4242+ <li><a class="text-pink underline" href="https://twitch.tv/hayden_dev">Twitch</a></li>
4343+ <li><a class="text-pink underline" href="mailto:hayden@hayden.moe">Email</a></li>
4444+ </ul>
4545+ </div>
4646+ <div class="flex flex-col gap-2">
4747+ <span class="font-bold text-lg">On this site</span>
4848+ <ul class="list-disc list-inside">
4949+ <li><a class="text-pink underline" href="/posts">Blog</a></li>
5050+ <li><a class="text-pink underline" href="/ring">Webring</a></li>
5151+ </ul>
5252+ </div>
5353+ </div>
5454+</Shell>
+36
src/pages/posts/[rkey].astro
···11+---
22+export const prerender = false;
33+44+import { createMarkdownProcessor } from '@astrojs/markdown-remark';
55+66+import { getPost } from '~/atproto/getPost';
77+import Shell from '~/layouts/shell.astro';
88+import FormattedDate from '~/components/formatted-date.astro';
99+1010+const rkey = Astro.params.rkey;
1111+1212+const markdownProcessor = await createMarkdownProcessor();
1313+const post = await getPost(Astro.locals, rkey!);
1414+const content = await markdownProcessor.render(post.content)
1515+---
1616+<Shell>
1717+ <header class="max-w-2xl mx-auto w-full">
1818+ <h1 class="font-bold before-hash-1">{post.title}</h1>
1919+ <span class="text-base03">
2020+ <FormattedDate date={new Date(Date.parse(post.createdAt))} />
2121+ </span>
2222+ </header>
2323+2424+ <hr />
2525+2626+ <div class="prose max-w-2xl mx-auto pb-5" set:html={content.code} />
2727+2828+ <hr />
2929+3030+ <p class="max-w-2xl mx-auto pb-5 prose">
3131+ Thanks for reading along, I hope you enjoyed this post.
3232+ If you did, maybe consider following me on <a href="https://bsky.app/profile/hayden.moe">Bluesky</a>,
3333+ and if you're feeling generous, maybe consider <a href="https://ko-fi.com/haydenuwu">buying me a coffee</a>.
3434+ I'm trying to write more this year, so I'll see you in the next post. 👋
3535+ </p>
3636+</Shell>