Remove auth/backend; convert entire site to static MDX blog#80
Remove auth/backend; convert entire site to static MDX blog#80
Conversation
Co-authored-by: ncrmro <8276365+ncrmro@users.noreply.github.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
ncrmro-website | 5cb47e6 | Mar 19 2026, 04:20 AM |
…ePath helpers Co-authored-by: ncrmro <8276365+ncrmro@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Converts the site from an authenticated, database-backed app into a fully static, file-driven MDX blog with published posts sourced from public/posts/.
Changes:
- Remove auth, database, dashboard UI, and content-mutation APIs/utilities (including R2 upload/sync tooling).
- Add a filesystem-based posts layer (
src/lib/posts.ts) and update post listing/tag pages + post detail rendering to use it. - Update runtime/config/tests/deps to reflect static operation (wrangler vars, Next config, Playwright tests, React 19).
Reviewed changes
Copilot reviewed 61 out of 67 changed files in this pull request and generated 17 comments.
Show a summary per file
| File | Description |
|---|---|
| wrangler.jsonc | Remove DB/auth/R2 vars and bucket bindings. |
| tests/posts.spec.ts | Replace CRUD/admin E2E with static blog smoke tests. |
| tests/journal_entry.spec.ts | Remove journal/dashboard E2E test. |
| tests/example.spec.ts | Remove login E2E test. |
| tests/admin_access.spec.ts | Remove dashboard access-control E2E tests. |
| src/models/posts.ts | Remove DB-backed post/tag/journal model layer. |
| src/lib/r2/upload.ts | Remove R2 upload utilities. |
| src/lib/r2/image-url.ts | Remove R2 image URL helpers. |
| src/lib/r2/check.ts | Remove R2 existence/listing utilities. |
| src/lib/posts.ts | Add filesystem-backed post loader/filtering/tag helpers. |
| src/lib/database.ts | Remove database re-export shim. |
| src/lib/auth.ts | Remove password/gravatar/auth helpers. |
| src/lib/auth.config.ts | Remove NextAuth edge config. |
| src/database/schema.users.ts | Remove Drizzle user/session schema. |
| src/database/schema.ts | Remove schema re-export entrypoint. |
| src/database/schema.posts.ts | Remove Drizzle posts/tags schema. |
| src/database/schema.journal.ts | Remove Drizzle journal schema. |
| src/database/index.ts | Remove DB client creation and helpers. |
| src/app/sitemap.xml/route.ts | Generate sitemap from filesystem posts instead of DB. |
| src/app/posts/types.ts | Update post type to match filesystem-backed shape. |
| src/app/posts/travel/page.tsx | Render travel tag page from filesystem posts. |
| src/app/posts/tech/page.tsx | Render tech tag page from filesystem posts (deduping tags). |
| src/app/posts/page.tsx | Make posts index no longer force dynamic rendering. |
| src/app/posts/food/page.tsx | Render food tag page from filesystem posts. |
| src/app/posts/actions.ts | Remove server action for MDX serialization. |
| src/app/posts/[slug]/page.tsx | Switch post page to SSG (generateStaticParams) + filesystem lookup. |
| src/app/posts/[slug]/Post.tsx | Convert post rendering to RSC MDX (next-mdx-remote/rsc) + local media paths. |
| src/app/posts/Posts.tsx | Use filesystem posts for the posts list. |
| src/app/posts/PostItem.tsx | Simplify post list items for the static blog. |
| src/app/page.tsx | Remove forced dynamic home rendering. |
| src/app/layout.tsx | Remove toast provider tied to removed dashboard flows. |
| src/app/dashboard/posts/page.tsx | Remove dashboard posts list UI. |
| src/app/dashboard/posts/new/page.tsx | Remove dashboard “new post” UI/action. |
| src/app/dashboard/posts/layout.tsx | Remove dashboard posts layout wrapper. |
| src/app/dashboard/posts/form_media.tsx | Remove dashboard media UI (R2-backed). |
| src/app/dashboard/posts/form_fields.tsx | Remove dashboard post metadata form fields. |
| src/app/dashboard/posts/form.tsx | Remove dashboard post editor form. |
| src/app/dashboard/posts/[slug]/page.tsx | Remove dashboard post edit page. |
| src/app/dashboard/posts/PostsFilters.tsx | Remove dashboard posts filtering UI. |
| src/app/dashboard/page.tsx | Remove dashboard landing page. |
| src/app/dashboard/layout.tsx | Remove dashboard auth-gated layout. |
| src/app/dashboard/layout.client.tsx | Remove dashboard client layout. |
| src/app/dashboard/journal/page.tsx | Remove journal UI/page. |
| src/app/dashboard/journal/layout.tsx | Remove journal layout. |
| src/app/dashboard/journal/form.tsx | Remove journal entry form. |
| src/app/auth.ts | Remove NextAuth setup and helpers. |
| src/app/api/posts/uploads/route.ts | Remove uploads API route. |
| src/app/api/posts/sync/route.ts | Remove posts sync API route. |
| src/app/api/auth/[...nextauth]/route.ts | Remove NextAuth route handler. |
| public/posts/2020_08_23_scytale-automated-private-key-infrastructure.md | Fix YAML frontmatter quoting for title. |
| playwright.fixtures.ts | Remove DB/auth-based Playwright fixtures. |
| package.json | Remove DB/auth/R2 deps; move to React 19/types. |
| next.config.js | Remove server runtime config and remote image patterns tied to auth/R2. |
| drizzle/meta/_journal.json | Remove Drizzle migration metadata. |
| drizzle/meta/0002_snapshot.json | Remove Drizzle schema snapshot. |
| drizzle/meta/0001_snapshot.json | Remove Drizzle schema snapshot. |
| drizzle/meta/0000_snapshot.json | Remove Drizzle schema snapshot. |
| drizzle/0002_cultured_marvel_apes.sql | Remove Drizzle migration SQL. |
| drizzle/0001_demonic_madelyne_pryor.sql | Remove Drizzle migration SQL. |
| drizzle/0000_lame_wendell_vaughn.sql | Remove Drizzle migration SQL. |
| drizzle.config.ts | Remove Drizzle kit configuration. |
| bin/sync-posts.ts | Remove posts sync CLI. |
| bin/migrate-images.ts | Remove R2 image migration script. |
| bin/generate-password.ts | Remove password generation/admin script. |
| bin/claude | Remove local Claude/MCP helper script. |
| REQUIREMENTS.md | Add RFC2119 requirements for the static filesystem-driven blog. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function getAllPosts(): PostData[] { const entries = fs.readdirSync(POSTS_DIR); | ||
| const posts: PostData[] = []; |
There was a problem hiding this comment.
Line 52 has export function getAllPosts(): PostData[] { const entries = ... on one line. This looks like an accidental formatting issue and may violate repo lint/prettier rules; please split the function body onto a new line for readability.
| code: (p: any) => { | ||
| // Check if this is a code block (has language class) or inline code | ||
| const isCodeBlock = p.className && p.className.startsWith('language-'); | ||
|
|
||
| const isCodeBlock = | ||
| p.className && p.className.startsWith("language-"); | ||
| if (isCodeBlock) { | ||
| // Use Highlight for syntax-highlighted code blocks with vertical spacing | ||
| return ( | ||
| <div className="my-4"> | ||
| <Highlight className={p.className}>{p.children}</Highlight> | ||
| </div> | ||
| <code | ||
| className="font-mono text-gray-800 dark:text-gray-200" | ||
| {...p} | ||
| /> |
There was a problem hiding this comment.
In the MDX code renderer, className is set and then {...p} is spread after it, which will override className (and any other props you set) when p.className exists. Spread props first or explicitly merge className values so code blocks keep the intended styling.
| Image: (p: any) => ( | ||
| <div style={{ display: "flex", justifyContent: "center" }}> | ||
| {/*// @ts-ignore/*/} | ||
| <NextImage | ||
| {...props} | ||
| <img | ||
| alt={p.alt} | ||
| width={500} | ||
| height={500} | ||
| src={`https://r2.ncrmro.com/uploads/posts/${props.post.id}/${p.src}`} | ||
| style={{ maxWidth: "100%", height: "auto" }} | ||
| src={getPostImagePath(props.post, p.src)} | ||
| /> |
There was a problem hiding this comment.
The MDX Image component renders an <img> with alt={p.alt}. If alt isn’t provided in MDX, this produces a missing/undefined alt attribute; for accessibility it’s better to default to an empty string (decorative) or require alt text. Also consider adding loading="lazy"/decoding="async" since this bypasses next/image optimizations.
| function TechnologyPosts() { | ||
| const postsList = getPostsByTag("food"); | ||
|
|
||
| return ( | ||
| <div></div> | ||
| // <PageLayout> | ||
| // <Posts posts={props.posts} /> | ||
| // </PageLayout> | ||
| <ul role="list" className="-mb-8"> | ||
| {postsList.map((post, index: number) => ( | ||
| <PostItem | ||
| key={post.slug} | ||
| post={post} | ||
| evenRow={index !== postsList.length - 1} | ||
| /> | ||
| ))} | ||
| </ul> | ||
| ); | ||
| } | ||
| // export const getStaticProps: GetStaticProps = async (context) => { | ||
| // return { | ||
| // props: { posts: [], } | ||
| // }; | ||
| // }; | ||
|
|
||
| export default TechnologyPosts; |
There was a problem hiding this comment.
This page is for the food category, but the component is still named TechnologyPosts/exported as TechnologyPosts, which is misleading for future maintenance. Rename the component/export to something food-specific (e.g., FoodPosts).
| <a | ||
| href={ | ||
| props.dashboard | ||
| ? `/dashboard/posts/${props.post.slug}` | ||
| : `/posts/${props.post.slug}` | ||
| } | ||
| href={`/posts/${props.post.slug}`} | ||
| className="font-medium text-gray-900 dark:text-white" | ||
| > | ||
| {props.post.title} | ||
| </a> |
There was a problem hiding this comment.
PostItem uses a plain <a href> for internal navigation to /posts/[slug]. In Next.js app router, using next/link avoids full page reloads and preserves client-side navigation behavior. Consider switching this to <Link href={...}> for consistency with other internal links (e.g., Navbar/projects pages).
| tags: frontmatter.tags | ||
| ? frontmatter.tags.split(",").map((t) => t.trim()) | ||
| : [], |
There was a problem hiding this comment.
tags parsing currently does split(',').map(trim) but doesn’t filter out empty strings, so frontmatter like tags: tech, will produce an empty tag entry. Consider adding a .filter(Boolean) after trimming to keep tag lists clean.
| import fs from "fs"; | ||
| import path from "path"; | ||
| import YAML from "yaml"; |
There was a problem hiding this comment.
Since this module imports Node fs and should never be bundled into client code, consider adding import "server-only"; at the top. This prevents accidental client imports that would break builds/runtime when fs is unavailable.
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ slug: string }>; | ||
| }): Promise<Metadata> { | ||
| const { slug } = await params; | ||
|
|
||
| const [postResult] = await db | ||
| .select({ | ||
| id: posts.id, | ||
| title: posts.title, | ||
| description: posts.description, | ||
| slug: posts.slug, | ||
| publishDate: posts.publishDate, | ||
| }) | ||
| .from(posts) | ||
| .where(eq(posts.slug, slug)); | ||
| const post = getPostBySlug(slug); |
There was a problem hiding this comment.
The page props type uses params: Promise<{ slug: string }> and then await params. In Next.js, params is passed as a plain object; await works but the typing is misleading and can confuse future changes. Consider typing params as { slug: string } and removing the unnecessary await.
| 1. The site MUST NOT require authentication to view any public page. | ||
| 2. The site MUST NOT require a database connection at runtime. | ||
| 3. The site MUST NOT expose any login, dashboard, or admin routes. | ||
| 4. The site MUST NOT include any server-side API routes for content mutation | ||
| (e.g. creating, editing, or deleting posts). |
There was a problem hiding this comment.
REQUIREMENTS.md states the site MUST NOT expose any dashboard/admin routes, but the UI still conditionally renders a Dashboard link in src/components/Navbar.tsx when a viewer_session cookie exists. To align the implementation with these requirements (and the PR’s stated removal of auth/admin), that link/cookie check should be removed as part of this change set.
| function parseFrontmatter(content: string): { | ||
| frontmatter: PostFrontmatter; | ||
| body: string; | ||
| } { | ||
| if (!content.startsWith("---")) { | ||
| return { frontmatter: {}, body: content }; | ||
| } | ||
| const end = content.indexOf("---", 3); | ||
| if (end === -1) { | ||
| return { frontmatter: {}, body: content }; | ||
| } | ||
| const yamlStr = content.slice(3, end).trim(); | ||
| const body = content.slice(end + 3).trim(); | ||
| const frontmatter = (YAML.parse(yamlStr) as PostFrontmatter) ?? {}; | ||
| return { frontmatter, body }; |
There was a problem hiding this comment.
parseFrontmatter() finds the closing delimiter via content.indexOf('---', 3), which can terminate early if --- appears in YAML values or near the top of the body (e.g., a Markdown horizontal rule). Consider matching the closing delimiter only when it appears on its own line (e.g., \n---\n) or using a frontmatter regex/parser so MDX content can safely include ---.
Eliminates all runtime dependencies (database, auth, R2 storage, dashboard) and converts the site to a fully static MDX blog driven by files in
public/posts/.Core changes
REQUIREMENTS.md— RFC 2119 spec for the static blog (content format, file naming, rendering, no-auth constraints)src/lib/posts.ts— new file-based post layer; readspublic/posts/**/*.md, parses YAML frontmatter viayaml, filters tostate: published, extracts slug/date from filename conventiongetAllPosts,getPostBySlug,getPostsByTag,getPostImagePathRendering
posts/[slug]/page.tsxusesgenerateStaticParams— all post pages are now SSGPost.tsxconverted to RSC usingnext-mdx-remote/rsc; inlineImageMDX component resolves to/posts/{dirName}/media/{src}/posts/tech,/travel,/food) usegetPostsByTagforce-dynamicfrom home and posts list pagesDeleted
src/app/auth.ts,src/app/api/auth/,src/lib/auth.*— NextAuthsrc/database/,src/models/,drizzle/,drizzle.config.ts— Drizzle + LibSQLsrc/app/dashboard/— entire admin UIsrc/app/api/posts/{uploads,sync}/— R2 upload and sync endpointssrc/lib/r2/,bin/— R2 utilities and CLI scriptsplaywright.fixtures.ts, auth/journal/CRUD testsDependencies
next-auth,drizzle-orm,@libsql/client,sonner,react-highlight,react-markdown,@aws-sdk/client-s3,posthog-node,drizzle-kit,sqld$$typeofsymbols caused RSC prerender failures withnext-mdx-remote/rsc)title: Scytale: …→ quoted)Config
wrangler.jsonc— removedTURSO_*,AUTH_*,NEXT_PUBLIC_R2_DOMAINvars and R2 bucket bindingnext.config.js— removedserverExternalPackages,serverRuntimeConfig, remote image patterns for gravatar/R2✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.