Full Cycle Next.js App
View Live Project
VertexBlog is a blogging platform based on the Windows 95 user interface. It runs as a file explorer directory where blogs are set up as folders within /Documents, and posts are text files within their respective blogs.
- System Info
- Blog Creation & Deletion
- Post Creation, Modification & Deletion
- Virtualized list scrolling (from scratch)
- Ratelimiting (User & IP-based, from scratch)
- Auth
Generic sign up, sign in & sign out process faciliated by Lucia auth.
- Users can sign up, in & out from within the start menu.
- Authentication goes through Lucia auth, which stores entries in our PostgreSQL database.
- Passwords are hashed with Argon2.
- Blogs & Text file mutation is auth-protected.
Users may create their own blogs (folders) within /Documents.
- In /Documents, pressing file > new blog opens a file creation prompt.
- After entering a blog title, the user may submit this "file" creation.
- Upon submission, it will be added to the database if it passes the following checks:
- User is authorized.
- User is not rate limited.
- Input passes validation.
- User does not have an existing blog & blog name isn't taken.
- revalidatePath().
- From within the blog itself: File > Delete Blog.
- Confirmation prompt will appear with the options "Delete", "Cancel" and "X".
- Cancel & X close the prompt. Delete checks the following:
- User is authorized.
- User is not rate limited.
- Input passes validation.
- Softdelete blog if exists & correct author (Softdelete to prevent blog name hijack after deletion for impersonation).
- Hard delete posts - not transactional as post deletion bugging shouldn't prevent blog deletion no matter what.
- revalidatePath()
Users may create posts from within their own blog folders.
- In blog, pressing file > new post opens a file creation prompt.
- After entering a post title, the user may submit this "file" creation.
- Upon submission, it will be added to the database if it passes the following checks:
- User is authorized.
- User is not rate limited.
- Input passes validation.
- Blog exists & correct author.
- Set blog to active (for filled folder icon).
- revalidatePath().
- Users can type within post text files.
- isDirty state is tracked for exit warnings and save prompting.
- File > Save will save the file if the following checks pass:
- User is authorized.
- User is not rate limited.
- Input passes validation.
- Verify existence & ownership.
- Updates post content.
- revalidatePath().
- In post > File > Delete.
- Confirmation prompt will appear with the options "Delete", "Cancel" and "X".
- Cancel & X close the prompt. Delete checks the following:
- User is authorized.
- User is not rate limited.
- Input passes validation.
- Verify existence & ownership.
- Delete post, if no more posts set blog to inactive (empty folder icon).
- revalidatePath().
- Windows can be dragged by their header areas.
- Windows can be resized & maximized.
- Windows can be closed.
- A context provider retains a z-index value - when a window is clicked the value is incremented and given to the target window. This allows accurate layer history to focus specific windows when interacted with.
- Files are rendered as flex lists within draggable windows.
- Files can be filtered by search.
- Files can be sorted by name, earliest creation date & last updated.
- File display can be changed between large icons, small icons & single-column list.
- Files are displayed as a virtualized list written from scratch, which allows the "rendering" of thousands of files at once without impacting scroll or window dragging performance.
- We do this by calculating the intended height occupation if all files were rendered depending on their current view mode (large, small, list). Then we detect which chunk of files should be rendered in the window depending on the current scroll position, and position that chunk in the correct position.
- Based on T3 Stack
- Next.js 15
- React 19.0.0-rc-69d4b800-20241021
- TypeScript 5.6.3
- Drizzle (PostgreSQL)
- Lucia (Auth)
- Argon2 (Hashing)
- Sentry (Error Management)
- Zod 3.23.8 (Validation)
- heroicons (Basic Icons)
- Vercel Hosting
MGSimard
X: @MGSimard
GitHub: @MGSimard
Mail: [email protected]
For more info, view my portfolio at mgsimard.dev.
- Update to latest Nextjs, react etc canaries
- Deploy to vercel
- Scaffold basic layout
- Desktop layout.tsx
- Documents folder (page or component) as explorer window
- Within, blog folders navigates the explorer window to that blog
- Figure out how to minimize "use client" impact of making file explorer draggable
- Make file explorer draggable on blue header area
- Constrain draggable elements to inner window
- Work on maximize button - maximized window shouldn't be draggable
- Create text file component
- Move signup process to a component within taskbar start menu instead of page
- Set up database (Vercel PostgreSQL w/ Drizzle ORM)
- Complain on Github about months-long multi-project schema drizzle bug that tries to kill your DB with a sequence drop
- Complain on Github about another driizzle bug where .default(false) & .default(sql
FALSE) don't work on boolean() - Wait until Drizzle makes use of their funding and unfucks these major fuckups
- Password hashing with Argon2, NIST guideline requirements
- Sign in, sign out
- Enter password twice prompt, crosscheck on FE & BE
- Ensure you communicate that leading and trailing spaces aren't allowed in password (html pattern="^[^ ].+[^ ]$")
- So validate that part on FE first, then on BE in case they bypass it on purpose
- Finish setting up auth (Lucia)
- Put auth in start menu
- Close start menu when clicking outside of it
- Button icons for close/maximize/not-maximize
- Text file components should render as draggable window
- Fix my piece of shit I key by desoldering page down and putting it in I's place
- Come up with empty folder, filled folder and notepad icons (shortcut and window header icons)
- Initial position & size of file explorer on first render
- Completely disable highlighting outside of notepad text
- Look at mobile not being able to use the native resize control - yeah that doesn't work on mobile shame I might still support dragging on mobile though
- Maybe add local clock in task bar? Could be dope if you use website on f11
- Create zindex context for window focus order on click
- Consider windows in task bar // NO since we opted out of doing minimization
- Check address bar not scaling down past 390px viewport width (I forgot I wanted min-width on windows I'm a clown)
- Use blog title for url slug and address slug (done, use encodeuricomponent and decodeuricomponent, don't really like what it looks like in browser URL bar though)
- Think about supporting window dragging on touch devices
- Create blog + server action
- Create post + server action
- Modify create post server action to only include title, then make new action triggered by SAVING a txt file
- Set up blog & post ordering
- Consolidate control buttons menu style stuff classes w/e with notepad too, also wrap notepad file btn in span and make that relative instead of entire bar
- Move blog and post creation into File button akin to how I have post saving set up
- Back button doesn't have the hover thing fix it clown
- Eliminate race conditions (spamming post submit creates multiple posts) - disable on pending, if user fucks with disabled... I mean enjoy the ratelimit and duplicate post titles - I do allow that. Dupe blog obv not an issue here since I do checks in backend.
- Keep track of state, if notepad file has been dirtied (modified) prompt a confirmation when they try to exit saying that the modifications haven't been saved yet. Onchange re-dirty the state, on save clean the state.
- Enable show password on creation and login (NIST)
- Set up toast for warning, success and confirmation windows
- Fix click gap between icon and text
- Autoroute user to newly created blog
- Icon view style functionality/context (Setting carries over in all folders)
- Put in the actual filepath in the save warning dialog
- ATTEMPT TO ROLL MY OWN RATELIMIT FROM SCRATCH, GOT ROUGH IDEA OF HOW IT CAN WORK
- Consider separate ratelimiting for action types (Signup/Signin, Data fetching, Data mutation (Blog/Post))
- Upgrade to new stable Next 15, migrate .eslintrc.cjs to flat config eslint.config.mjs etc etc.
- Reduce filesize of "/favicons/android-chrome-512x512.png"
- Some form of pagination for blogs and maybe posts, dragging performance sucks once there are a lot of files in the div - (Settled on virtualization from scratch)
- Implement virtualized list scrolling from scratch
- Now that we have virtualization, need to fix file creation if scroll isn't at the top already
- Suspense the page loads, allow shortcut render popin
- Use the isDirty prompting thing if user tries to navigate away from page
- Look into date locale mismatching between server and user client
- Create a shortcut for a dxdiag type thing that lists info about the site
- Post deletion
- Works, now make action tighter and use transactions
- Now run the delete button onto a confirmation lol
- DISABLE ALL EVENTS OUTSIDE OF CONFIRMATION & ERROR POPUPS. CURRENTLY YOU CAN SPAM ENTER TO MAKE NEW POPUPS (no effect, would just be cleaner to prevent it)
- Blog deletion
- Make sure deletion also uses dialog prompts/confirm/error
- Set up blog & post search filtering
- Now make it look good and responsive
- Make decent 404 page
- Metadata, especially important for blog links
- Sentry mayhaps?
- Style new list setup for multi-errors in dialog window
- IP-based ratelimit for guest users
- Now clean up repeated code for getClientIdentifier, then add ratelimit to fetch actions
- Save warning on ahref click
- Consider focus trap in dialog (right now can exit it, then re-entering site focuses first dom node and can't re-enter dialog with tab because background event disabled on purpose)
- position fixed bottom stuff has been broken on firefox mobile for years, look into it because that breaks taskbar when zoom + drag
- Make ograph images for diff platforms
