Deno Dino with a cap and tattoo

Vibing with a Cursor agent ✨ 🤖

Creating a personal blog that allows me to easily write my thoughts in Markdown and share them with the world has been on my todolist for a longggg time. This weekend I thought I'd use that as an excuse to also familiarize myself with the agent mode of Cursor.

Note that I've mostly used Cursor's AI auto-selection to see how well Cursor can perform with minimal setup (and ofcourse to optimize credits spend).

Starting off

To start off with, I chose to build with a web framework (Fresh) and JS runtime (Deno) that I am not an expert in, but definitely know enough about to be able to identify what is good and bad. I have been itching to find an opportunity to use Deno for a while so I thought why not now 😁. I used the Deno CLI to scaffold a new Fresh project, with Tailwind for styling as a starting point before involving any AI assistance.

From there, I handed over the keys to Cursor as I wanted to vibe code my way to a finished product. I prompted that I wanted to turn this project into personal website for myself which had some links to my social media and CV, a dark mode toggle, and that its design should be inspired by the Fresh website. It did this very quickly and I probably had something sooner than I would have if I was doing this manually, awesome!

Initial homepage screenshot

Next was to turn this from a single page project into an actual blog. Using Cursor I was able to give a few prompts that roughly went like this:

  • add a 404 page for an unhandled routes on the project. It should look like the homepage and have a link back to the homepage.
  • add a directory containing blogs written in Markdown, that can then be viewed by visiting /blog/<blog-slug> or /blog to see all. Minimize use of new dependencies and prioritise Deno standard library functionalities where possible.

Cursor did a great job with all of this and only had a couple hiccups with the classic Cannot read blah on undefined JS problem 😂. I'm sure I'd have written the same bug on my first go too. And telling Cursor what had happened, it was able to fix it and also self verify the fix (when asked) by curl-ing the page.

At this point, the site was functional but had some design inconsistencies across it so I asked Cursor to help me with some refactoring. I mentioned that I wanted to extract a common Header. This is where Cursor first started to struggle.

Initial homepage screenshot

It created a new components directory and added a Header component to it, makes sense. But when viewing the page, I saw the styling had changed and my theme switcher was no longer on the top right of the page. I asked Cursor to fix this a few times and also tried giving it screenshots of whats wrong, but it kept proposing changes that didn't really do anything.

After a while, I groaned and decided to give it a go myself via Chrome dev tools to set some inline styles. Turns out the Header had a flex container but the first element in it was missing the flex-grow attribute so it didn't fill up the space correctly. I explained this to Cursor, and it argued with me and it kept proposing different solutions that didn't do anything 🤦🏾‍♂️. At this point, I mentioned to it that using an inline style via the browser worked and asked for some ideas on what might be wrong (rather than asking Cursor to fix straight away). It gave me a few options, and upon reading them it was clear to me what had gone wrong. Cursor had created some new components in a new directory, but hadn't updated the tailwind config to look for content in that directory. This highlighted to me that when Cursor is struggling to do something, it may not be looking outside that file/area its working in to figure out what might be going wrong (possibly to manage how much context it carries?). Once I pointed out we should update the Tailwind config, it was able to do this easily and finally our header was looking good 🎉. Good to know even AI struggles with positioning a div correctly.

Initial homepage screenshot

After the refactoring was complete, it became the standard case of tidying up the code and improving the business logic. Throughout this process, I noticed several interesting patterns and learnings that I think are worth sharing:

  • Cursor hadn't picked the latest versions of the libraries it had installed for Markdown parsing. I had to notice this myself and then prompt it to update the library. Its also worth asking for alternatives to ensure its picked the right library for your use case.
  • Cursor was good at proposing UI design solutions to problems I wasn't sure on how best to address e.g. where to add navigation from a blog to the parent blogs page, styling things to look good ✨.
  • Cursor didn't handle error cases so thinking through what may go wrong is still an important muscle to use as an engineer e.g what to do if a blog slug is invalid.
  • Fresh best practices recommend data fetching in route handlers, but Cursor had done this directly in the component itself. This highlighted how important it is to understand framework/system principles, even when "vibe coding" with AI for anything you want keep maintainable long term.
  • It's worth being selective about what you ask the agent to do - for example, while AI agents can handle committing on your behalf with good messages, the credits and waiting time didn't feel worth it.
  • Chat sessions can get stale - refreshing them periodically helps avoid biased responses.
  • While I skipped tests for this project, visual regression testing would have been valuable given how many UI changes I was making. Playwright's a great tool for this but not worth it for a personal project like this.
    • Update - Since originally writing this, I worked on a UI bugfix at work with Claude 3.7. It fixed the issue visually based on my prompt—but also introduced subtle spacing changes I hadn’t noticed. Thankfully, our visual regression suite caught this. A strong reminder that testing is still essential, even with great AI help.

Key takeaways 🥡 🥤

After spending time building this blog with Cursor's agent mode, here are my most important learnings:

  • You can use the agent to perform actions on your behalf, like verification of a fix, writing commit messages, etc. However ensure you balance the use of this with your time, credits, and the complexity of the task.

  • An agent may not always think holistically when trying to solve a problem. If it is struggling to do something, its worth prompting to consider other parts of your application that may be relevant. Timeboxing how long you let it be stuck for before getting into the code yourself is another thing to keep in mind.

  • When an agent adds new dependencies to your project, take the time to review what option it has picked and ask it to justify its choice vs what else it considered. Its first choice may not be the best.

  • Cursor's model auto-selection is great. At one point I switched to using Claude 3.5 (typically I find 3.7 can be slow, even though its smarter) but didn't notice a major shift in agent quality or behaviour.

  • If you're vibing, make sure you periodically review the code and details of what's being added. Context and how a project is maintained are details an agent may not always consider. And don't be that person who pushes AI code to pull requests without having reviewed it yourself.

  • This experience with Cursor has shown me that VSCode + Copilot in agent mode can give you a very similar experience at this stage. Which is awesome as most companies are investing in Copilot as its easy to get whilst on an enterprise GitHub plan. This could change over time though so I'll be revisiting Cursor again in a month or two.

Overall though, I’m very impressed with Cursor’s agent mode experience and will definitely try using it again in the near future. While it's not perfect, it's a powerful tool that can significantly speed up development when used thoughtfully. For now though, it’s safe to say engineers aren’t going anywhere 😌.