Turning my website into a desktop

· nextjs, react, portfolio, design, performance

Turning my website into a desktop

How I rebuilt my portfolio around windows, a taskbar, and a terminal while keeping real URLs and server-rendered blog posts.

My portfolio had become a collection of pages: an introduction, projects, a résumé, and a blog. I wanted the site itself to feel like something I would build. Since much of my work involves terminals and developer tools, I turned it into a small desktop.

Projects open in windows. Posts have a reader. A taskbar tracks what's open, and a terminal lets visitors explore the same content with commands. The welcome window still gives someone a quick introduction and a direct way to browse projects. Exploring is optional.

Giving each part of the site a home

The desktop metaphor gave the existing content a place:

ContentDesktop application
Introduction and featured workWelcome
Background and skillsREADME.md
Project list and detailsProjects
Blog index and articlesWriting and the post reader
Work and educationRésumé
Contact informationContact
Command-based navigationTerminal

The visual treatment is simple: pale wallpaper, dark borders, a purple active title bar, and compact controls. A note in the corner shows what I'm doing and links to the newest post.

A window manager in React

The window manager uses a reducer. Each window has an ID, a stacking order, optional position and size, and minimized and maximized flags. Opening a window either creates it or brings the existing one forward.

The reducer handles actions such as open, focus, close, minimize, toggleMax, and rect. The taskbar reads the same state, so a minimized window can be restored without rebuilding a separate list of open applications.

ActionState change
OpenAdd a window or restore an existing one
FocusMove it above the other windows
MinimizeHide it while retaining its state
MaximizeFill the area above the taskbar
Move or resizeSave the final rectangle
CloseRemove it from the open-window list

Dragging needs more care than a button click. Pointer movement writes the window's position directly to its element. React receives the final rectangle when the drag ends. That avoids rerendering the window contents for every pointer event.

The same interaction supports snapping at the screen edges. A temporary outline shows the proposed placement before the window is released.

Keeping real URLs

A desktop interface can make navigation awkward if every view lives behind the same address. I kept routes for the main sections and /blog/[slug] for individual posts.

On a direct visit, the pathname determines the initial window. During a session, the focused window can update the address through the browser history API. Opening a post uses the Next.js router because it needs the article from that route.

The desktop lives in the root layout. The post route supplies server-rendered children to its reader window. That lets the desktop keep its open windows while an article changes.

Making the terminal useful

The terminal exposes a small read-only filesystem built from the site's data. Project directories contain a README.md; the writing directory contains one entry per post. Some commands to try:

ls
cat README.md
cd projects
ls
cd ~/writing
open desktop-os-website.md

It supports command history, tab completion, path resolution, and clickable suggestions. Plain-English questions go through local pattern matching and return the relevant profile or project information. There is no model request behind those answers.

Both the desktop and terminal read the same project and post data. Adding an article creates a writing entry without a second list to maintain.

Search and smaller screens

⌘K or Ctrl+K opens a search dialog for apps, projects, posts, and actions. A backtick toggles the terminal when the visitor isn't typing into a field.

The search uses a native modal dialog to keep keyboard focus inside it. When a window comes forward, it can focus its main input. Text fields show the caret without drawing a box around the typing area; buttons and links retain visible keyboard focus.

Below 768 pixels, windows fill the available desktop area. Dragging is disabled there. The title controls and taskbar remain available, so a phone doesn't need to imitate a mouse-driven desktop.

Getting article loading out of the way

I kept the posts in Markdown. gray-matter reads their metadata, and the post route compiles their bodies with next-mdx-remote, remark-gfm, and syntax highlighting. generateStaticParams lists the posts for the production build.

The writing index and latest-post link use Next.js links to prefetch articles. On a local production check, opening a prefetched post needed no new route request. That's more useful than making the loading overlay look faster.

There was also a sequencing problem. The reader's mount notification could run before the asynchronous Markdown body was ready. The route now waits for compilation before returning the article, so the header and body arrive together.

Code blocks stay in server-rendered markup. Only their copy buttons need client state. For architecture sketches, I replaced misaligned box-drawing characters with Markdown tables and numbered flows. Those render as part of the article and stay readable in a narrow window.

Loading application code when it's needed

The welcome window ships with the initial page. Other application bodies use dynamic imports, so a visitor reading an introduction doesn't need to load the terminal implementation immediately.

The window-opening animation uses opacity and a transform, and respects reduced-motion preferences. Most of the interface is regular HTML and CSS; the desktop behavior doesn't require an animation framework.

I checked the production build, typed commands in the terminal, opened posts through the desktop, and checked the table layouts at desktop and phone widths. React Doctor is a useful additional code check, but it doesn't measure navigation latency or tell me whether a diagram is readable.

The files behind it

File or directoryResponsibility
src/components/desktop/Desktop.tsxCompose the desktop and connect it to routing
src/components/desktop/state.tsWindow state and reducer actions
src/components/desktop/Window.tsxWindow frame, focus, dragging, and resizing
src/components/desktop/terminal/Read-only filesystem and command handling
src/app/blog/[slug]/page.tsxCompile and render an article
src/content/posts/Markdown source and frontmatter

The part I like most is that there are several ways into the same work. Someone can click a project, search for a topic, or type ls. They all lead back to the same content.

Website source

Turning my website into a desktop