Overview
In January 2024, I landed one of my first freelance gigs, building a website for my sister's makeup artistry business, MIROA Makeup. Join me for a glimpse into the journey Michelle and I undertook, along with the valuable lessons that have enriched my skills as a designer and software engineer.
The Design Phase
Michelle aimed for a clean and timeless look that aligned with her brand's aesthetic. With assets from a graphic designer and photographer in hand, I had everything I needed to bring her vision to life.
Before diving into Figma, I conducted thorough research, seeking inspiration and identifying essential elements for a portfolio like MIROA's. With insights gathered, I began crafting designs in a new file. Despite this being a project for my sister, I approached it professionally, establishing a collaborative system. Iterating together, Michelle provided feedback on each page design, ensuring the site reflected her vision. Involving her in the creation process was special, as it allowed her to shape something representing her brand. My goal was for her to feel satisfied and rewarded with the final result, mirroring my own anticipation for the site's launch!
Within a few weeks, I meticulously reviewed every one of Michelle's comments and requested features, ultimately finalizing designs for both mobile and desktop versions. Leveraging Figma to its fullest, I optimized efficiency and visual consistency by establishing local styles and components, many of which were wrapped in auto-layouts. Take a look around the embedded Figma file below! It's worth noting that some designs were altered during development, leading to discrepancies between the mockups and the finalized site. Additionally, to the far left, you'll notice some designs that I created but ultimately did not make the cut.
The Tech Stack
After wrapping up the design phase in Figma , I shifted roles from designer to engineer. Astro is my framework of choice for building static sites due to its speed, flexibility, and exceptional developer experience. It seemed fitting for this project, especially since I planned each page to be statically generated (SSG) except the booking page, which required server-side rendering (SSR). Additionally, I opted to use Svelte for nearly all of my custom components, Tailwind CSS for styling, React for my React Email templates, Resend for email functionality and audience management, TypeScript for type safety and IntelliSense, Vercel for deployment, and Google Analytics for valuable user insight.
The Booking Page
While every aspect of this site contributed to my growth as a designer and engineer, one particular part stands out—the booking page. This page not only presented the greatest challenge but also offered invaluable lessons that significantly refined my skills.
Dynamic Processing and Validation
In an effort to maintain a clean aesthetic and tailor the booking form to the user's needs, I integrated conditional logic. This functionality dynamically reveals additional fields depending on the makeup service selected by the user. For example, selecting the Bridal or Special Occasions service prompts users to upload a reference image of their desired makeup look.
After this, my focus shifted to enhancing the security of the API endpoint. Committed to secure development practices, I integrated server-side validation using JOI and custom methods to strengthen the validation process.
I proceeded with the remainder of my implementation. However, while working on handling uploaded images (in cases where they existed), I inadvertently made a crucial mistake. Little did I know that this error would later lead to hours spent scouring through Stack Overflow entries. However, let's hold onto this incident for now, as I'll delve into it later.
Setting Up The Emails
I then crafted two distinct email templates using React Email: one for the inquiry email addressed to the admin at MIROA Makeup, and the other for the booking confirmation dispatched to clients upon booking. Employing Resend, I automated the process of sending both emails and added the client's email to a contact audience. This streamlined approach allows for an admin at MIROA to effortlessly send batch emails in the future if necessary.
Writing Efficient Code
Now, recalling the crucial mistake I mentioned earlier, after ensuring the site worked flawlessly locally and was ready for testing in a production environment, I deployed it on Vercel. I then proceeded to test the booking form on both desktop and mobile devices. While everything ran smoothly on desktop, a different story unfolded on mobile. Specifically, forms requiring an image took an eternity to process, triggering an HTTP 500 error I had previously set up. Upon inspecting the Vercel logs, I discovered that the issue stemmed from the API request taking too long to process, exceeding Vercel's 10-second ceiling limit for my account.
I spent the next few hours scouring through resources trying to pinpoint the issue, but nothing shed light on why mobile was causing such an edge case. After a while, I decided to step away from my laptop and return later with a fresh perspective. While out on a walk, a thought struck me: "What if my code isn't fast enough?" I pondered over the various processes—validating fields, sending out emails, adding users to contact lists—and questioned how I could speed up the execution of these tasks.
Upon returning to my laptop and conducting further research, I stumbled upon the Promise.all() method. I discovered that I could utilize this to concurrently run certain processes instead of awaiting them individually. This approach seemed promising, offering the potential to significantly reduce the overall processing time. Naturally, I implemented logic to ensure that if the inquiry email failed to send, the entire booking process would fail, and an error would be displayed to the user. This step was crucial to prevent scenarios where the client received a successful booking confirmation, but MIROA did not receive any notification, potentially resulting in an unhappy client who didn't receive their services.
I pushed my changes and tested the forms once more. While I did notice a slight improvement in processing time, it wasn't sufficient to prevent Vercel from erroring out. It was back to the drawing board for me...
Image Compression
I don't think that I'll ever forget the moment when it dawned on me: "Why not compress the image before sending it with the emails?" It was a facepalm-worthy realization of a missed step that likely led to prolonged processing times. With renewed determination, I installed Sharp to resize and reduce the image quality, ensuring a balance between minimal visual changes for the admin and more manageable file sizes for processing.
After another round of deployment, processing times were halved, and errors ceased to occur. The root cause was traced back to the oversight of massive image file sizes commonly produced by mobile phones. Consequently, when the cloud function received the request, the buffer size exceeded limits, resulting in prolonged email sending times—an explanation for the initial processing delays.
Conclusion
There are far too many valuable lessons from this project to condense into one page, and I owe it all to Michelle's trust and opportunity. A special thanks to her! I'm immensely proud of what we've achieved together. Feel free to visit the site now!