Enhancing Website Features with Voice Input and Security
Implemented rate limiting, SVG animated background, voice-to-text functionality, and more
Managed admin state on the client side using React Context to resolve server/client cookie sync issues
Encountered classic Next.js server/client cookie sync issue, resolved by using React Context
Website Feature Upgrades
Today I continued improving my newly launched website with a focus on security, admin workflow, and better state management.
The first improvement was adding rate limiting to /admin/login. I implemented a simple in-memory limiter in pure Next.js that allows five attempts per IP within a fifteen minute window. This closes the door on brute-force login attempts without introducing any external dependencies.
I also built a subtle animated SVG background as a reusable React component. It uses pure SVG and CSS keyframes, so there is no JavaScript overhead. The slow moving curved lines give the homepage a modern, data flow aesthetic while remaining lightweight and unobtrusive.
Inside /admin, I integrated the browser’s native SpeechRecognition API to enable voice to text. I love the fact that there is no API key, no cost, and no external service involved. Now as the admin, I can now speak and have the content transcribed directly into the input field when drafting a new post. That's truely a time saver.
I also implemented conditional rendering for admin controls. When the admin cookie is present, a New Post button appears on the homepage and an Edit button appears on each post page.
I built a protected route at /admin/edit/[slug] with a split layout: a markdown editor on the left and a live preview on the right. Saving commits the updated MDX file directly to GitHub. Git remains the single source of truth. The workflow is simple: edit, commit to GitHub, Vercel deploys, and the updated content goes live shortly after.
The Blocker
For admin state handling, I initially relied on server-side cookie checks to conditionally render the navigation and admin controls. The Nav would show “Admin” when logged out and “Logout” when logged in. However, this exposed a classic Next.js server and client cookie synchronization issue. After login, the cookie updated correctly, but the Nav did not re-render because server components do not automatically react to client-triggered cookie changes. Using router.refresh() helped, but it introduced race conditions since it is asynchronous and provides no completion signal.
The proper solution was to manage admin UI state on the client using React Context. Authentication remains secure and server-side, but shared UI state is handled in the client. The provider reads the cookie on first load to seed its initial state, and setIsAdmin(true or false) updates the navigation instantly without refresh hacks or window reloads. This cleanly separates security from presentation logic.