My 7-Day Journal Rebuilding a Medical Shop with MyMedi HTML Template

by

Building a Faster Medical Store: My 7-Day Experience with MyMedi HTML

A few weeks ago, a client of mine who runs a local medical supply shop called me in a bit of a panic. Their existing online shop, built on a heavy database-driven content management system, was taking over six seconds to load on mobile devices. In the medical supply niche, customers are often in a hurry. They need to order bandages, digital thermometers, or diagnostic kits quickly, and they do not want to wait for heavy scripts to load. The slow load times were hurting their conversion rates, and their bounce rate was climbing past sixty percent.

After checking their server logs, I realized the problem wasn't just their hosting. It was the massive pile of database queries, dynamic plugins, and unoptimized assets loading on every page view. I suggested a clean, modern approach: building a static, ultra-fast front-end storefront that interfaces with a lightweight backend API. To make this work without spending hundreds of hours designing layouts from scratch, we decided to look for a clean, professional that we could adapt, optimize, and connect to our lightweight data source.

We chose the because it was specifically tailored for medical stores, pharmacies, and health equipment suppliers. It had the clean, white-and-blue layouts that build trust, plus pre-built pages for grids, lists, single products, and checkout screens. This is my honest, step-by-step diary of how I spent seven days customizing, optimizing, and deploying this template for my client's shop, including the technical friction points I faced and the exact code I wrote to solve them.

---

Day 1: Unpacking, Setup, and the Initial Audit

I started my Monday by downloading the template files and setting up a basic local development environment using a simple local Node.js server. The structure of the template was quite straightforward. Inside the main folder, I found several HTML files for different homepage layouts, detailed product pages, blog layouts, cart, and checkout processes. The style and script files were organized neatly inside an assets folder containing subfolders for CSS, JS, and vendor plugins like Bootstrap and Slick slider.

My first step with any template is to run a basic Lighthouse speed audit on the raw, unmodified files. I spun up a local HTTP server using the http-server package in Node.js and pointed my browser to the index page. Here is the simple command I ran in my terminal:

# Install http-server globally if you don't have it
npm install -g http-server

# Spin up the server on port 8080
http-server -p 8080

Once the local page was running, I opened Chrome DevTools, clicked on the Lighthouse tab, set the device to "Mobile," and ran the analysis. The initial scores were decent, but they definitely had room for improvement on the performance side. The desktop version scored around eighty-eight, but on mobile, it dipped to sixty-nine. The main culprits were render-blocking stylesheets, large uncompressed image placeholders, and a bit of unused CSS from the standard Bootstrap framework included with the template.

I sat down with a cup of coffee and drew up a plan for the week. I wanted to keep the template's gorgeous aesthetic—with its clean product cards, neat medical icons, and clear shopping pathways—but I needed to strip out every single byte of unused asset code, optimize the image handling, and implement a fast, client-side shopping cart using local storage so that we wouldn't need a heavy database connection for simple browsing and cart management.

---

Day 2: Streamlining CSS and Optimizing the Asset Build Pipe

On Tuesday, I focused entirely on the stylesheets and build tools. The template utilizes Bootstrap for its responsive grid and core components. While Bootstrap is highly reliable and makes mobile layouts incredibly simple, it also comes with a lot of CSS classes that this specific project was never going to use. For example, we did not need Bootstrap's progress bars, alerts, or complex card layouts, as the template had its own highly optimized CSS rules for these elements.

To clean this up without manually scanning thousands of lines of code, I set up a simple Gulp-based build process. I created a package.json file in the root of my project and installed Gulp, PurgeCSS, and a couple of minification utilities. Here is the configuration I set up in my gulpfile.js:

const gulp = require('gulp');
const purgecss = require('gulp-purgecss');
const cleanCSS = require('gulp-clean-css');
const rename = require('gulp-rename');

gulp.task('optimize-css', () => {
    return gulp.src('assets/css/*.css')
        .pipe(purgecss({
            content: ['*.html', 'assets/js/**/*.js'],
            safelist: [
                'active', 
                'show', 
                'slick-active', 
                'slick-slide', 
                'cart-open', 
                'search-active'
            ]
        }))
        .pipe(cleanCSS({ level: 2 }))
        .pipe(rename({ suffix: '.min' }))
        .pipe(gulp.dest('dist/css'));
});

gulp.task('default', gulp.series('optimize-css'));

The trick here was using the safelist option in PurgeCSS. When templates use jQuery or vanilla JavaScript to toggle navigation menus, product tabs, or dynamic shopping carts, they often add active classes like .active or .show to elements on the fly. If you do not safelist these classes, PurgeCSS will assume they are unused because they do not appear in the static HTML files, and it will strip them out, breaking your mobile menus or shopping cart slide-outs. After running this task, the main stylesheet size dropped from over two hundred kilobytes to just thirty-four kilobytes, which made a massive difference in mobile loading speeds.

---

Day 3: Solving the Micro Technical Friction (Product Image Aspect Ratio Issue)

By Wednesday, I ran into my first real technical hurdle. The template's product cards were designed to display beautiful medical device images in a clean grid. However, the original CSS rules for the product thumbnail containers had a somewhat rigid height setup. When I loaded my client's real product photos into the markup, some of the images looked horribly squashed, while others with tall proportions (like syrup bottles) overflowed their containers and pushed the product pricing and title text out of alignment.

In a real store, you cannot control the exact shape of every supplier image. Some images are perfectly square, some are wide boxes of medical gloves, and others are tall bottles of hand sanitizer. I needed a flexible, modern CSS solution that would handle any image ratio elegantly without distorting the visual layout of the grid, and without requiring the client to manually crop twelve hundred images in Photoshop.

To fix this, I stripped out the fixed heights on the .product-thumbnail container and introduced a modern CSS aspect-ratio approach combined with object-fit: contain. I also wrote a small vanilla JavaScript helper that ran on page load. If an image had a transparent background and didn't fit the aspect ratio perfectly, the helper would add a subtle, light gray background to the card so that the product didn't look like it was floating awkwardly in empty space. Here is the CSS and JavaScript code I implemented:

/* Optimized Product Card Styles */
.product-thumbnail {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1; /* Keep it perfectly square */
    background-color: #fcfcfc;
    border-radius: 8px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 10px;
    box-sizing: border-box;
    transition: background-color 0.3s ease;
}

.product-thumbnail img {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    object-fit: contain; /* Prevent image stretching */
    transition: transform 0.3s ease;
}

/* Hover effect for a premium feel */
.product-thumbnail:hover img {
    transform: scale(1.05);
}

And here is the small JavaScript snippet I added to assets/js/custom-app.js to handle dynamic image container adjustments if an image had trouble loading or loaded with unusual proportions:

document.addEventListener("DOMContentLoaded", () => {
    const productImages = document.querySelectorAll(".product-thumbnail img");

    productImages.forEach(img => {
        // If image is already cached/loaded
        if (img.complete) {
            adjustContainer(img);
        } else {
            img.addEventListener("load", () => adjustContainer(img));
        }
    });

    function adjustContainer(image) {
        const width = image.naturalWidth;
        const height = image.naturalHeight;
        
        // If the image is extremely wide, adjust padding to keep it centered and neat
        if (width > height * 1.5) {
            image.parentElement.style.padding = "20px 10px";
        }
    }
});

This simple fix made the entire store look incredibly clean and robust, regardless of whether the product image was a wide box of face masks or a tiny, thin medical thermometer pen. The images no longer stretched or broke the visual grid alignment, and we avoided having to write any heavy server-side image processing scripts.

---

Day 4: Creating a Local-First Dynamic Shopping Cart

On Thursday, I tackled the core shopping experience. The template had gorgeous UI elements for the mini-cart sidebar, the main cart page, and the checkout form. However, because it was a static HTML template, these components did not actually do anything when you clicked "Add to Cart." I needed to connect these interface elements to a robust client-side shopping cart system that saved progress to the browser's localStorage.

Instead of pulling in a massive JavaScript framework like React or Vue, which would completely defeat our speed goals, I wrote a lightweight vanilla JavaScript cart manager. This manager listened for click events on any "Add to Cart" button, updated an array stored in the user's browser, and dynamically re-rendered the template's HTML cart elements without causing a single page reload. Here is the modular script I wrote to manage this:

const MiniCart = {
    storageKey: 'mymedi_cart_items',
    items: [],

    init() {
        this.loadCart();
        this.bindEvents();
        this.render();
    },

    loadCart() {
        const stored = localStorage.getItem(this.storageKey);
        this.items = stored ? JSON.parse(stored) : [];
    },

    saveCart() {
        localStorage.setItem(this.storageKey, JSON.stringify(this.items));
        this.render();
    },

    bindEvents() {
        document.addEventListener('click', (e) => {
            const btn = e.target.closest('.add-to-cart-btn');
            if (btn) {
                e.preventDefault();
                const product = {
                    id: btn.dataset.productId,
                    name: btn.dataset.productName,
                    price: parseFloat(btn.dataset.productPrice),
                    image: btn.dataset.productImage,
                    qty: 1
                };
                this.addItem(product);
            }
        });
    },

    addItem(newItem) {
        const existing = this.items.find(item => item.id === newItem.id);
        if (existing) {
            existing.qty += 1;
        } else {
            this.items.push(newItem);
        }
        this.saveCart();
        this.openCartDrawer();
    },

    openCartDrawer() {
        const drawer = document.querySelector('.cart-canvas-drawer');
        if (drawer) {
            drawer.classList.add('active');
            document.body.classList.add('cart-open');
        }
    },

    render() {
        const cartCountElements = document.querySelectorAll('.cart-count');
        const cartItemsContainer = document.querySelector('.cart-items-list');
        const cartTotalElement = document.querySelector('.cart-subtotal-amount');

        // Update count badges
        const totalQty = this.items.reduce((sum, item) => sum + item.qty, 0);
        cartCountElements.forEach(el => el.textContent = totalQty);

        // Update list
        if (cartItemsContainer) {
            if (this.items.length === 0) {
                cartItemsContainer.innerHTML = '<li class="empty-msg">Your cart is empty.</li>';
            } else {
                cartItemsContainer.innerHTML = this.items.map(item => `
                    <li class="cart-item-row" data-id="${item.id}">
                        <img src="${item.image}" alt="${item.name}" class="cart-item-img" />
                        <div class="cart-item-details">
                            <h4>${item.name}</h4>
                            <span class="cart-item-price">$${item.price.toFixed(2)} x ${item.qty}</span>
                        </div>
                    </li>
                `).join('');
            }
        }

        // Update total
        if (cartTotalElement) {
            const totalSum = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
            cartTotalElement.textContent = `$${totalSum.toFixed(2)}`;
        }
    }
};

document.addEventListener('DOMContentLoaded', () => MiniCart.init());

Implementing this lightweight cart meant that whenever a doctor or a patient clicked "Add to Cart" on a box of syringes or a blood pressure monitor, the item instantly slid into their sidebar cart with zero delay. It made the entire store feel like a custom-coded web app rather than a simple static site, and the performance hit was completely negligible.

---

Day 5: Nginx Caching Rules and Server Optimization

On Friday, I moved from the codebase to the server configuration. To get the fastest possible load times for our new medical shop, we deployed the static front-end files to a lightweight cloud VPS running an Nginx web server. Because the HTML, CSS, and JS files were now mostly static, we had an incredible opportunity to leverage aggressive server-side caching rules.

I logged into the server via SSH and opened the Nginx site configuration file. I wanted to make sure that we were serving pre-compressed assets using gzip or Brotli, and that we set long-term cache headers for all static media, fonts, and scripts so that repeat visitors would load the site instantly from their browser's local cache.

Here is the exact server block configuration I implemented in our Nginx config file:

server {
    listen 80;
    server_name store.myclientmedical.com;
    root /var/www/myclientmedical;
    index index.html;

    # Enable Gzip Compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
    gzip_min_length 1000;
    gzip_comp_level 6;

    # Media Caching (Images, WebP, SVGs)
    location ~* \.(jpg|jpeg|png|gif|webp|svg|ico)$ {
        expires 365d;
        add_header Cache-Control "public, no-transform";
        access_log off;
    }

    # Font and Asset Caching
    location ~* \.(woff|woff2|ttf|otf|css|js)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
        access_log off;
    }

    # Fallback to main index for clean routing if needed
    location / {
        try_files $uri $uri/ =404;
    }
}

With this configuration active, any visitor returning to the site to re-order supplies would load the styles, scripts, and logos directly from their local machine, resulting in load times of under three hundred milliseconds for repeat visits. This is the kind of speed that simply cannot be achieved with traditional, heavy database-driven websites without spending thousands of dollars on enterprise-grade enterprise-level application delivery networks.

---

Day 6: Mobile Touch Target and Accessibility Adjustments

On Saturday, I spent my morning testing the mobile experience across several physical devices, including an older Android phone and a couple of iPads. Mobile optimization is about much more than just responsive grid layout; it is about physical usability. People in medical environments are often moving quickly, wearing gloves, or multitasking, so touch targets must be easy to hit.

I noticed that the standard product filter sidebar dropdowns in the mobile template were a little bit tight. If you had larger fingers, it was easy to accidentally tap "Cardiology" when you meant to select "Dental Tools." I spent a couple of hours widening the touch target areas by modifying the mobile CSS padding rules.

I adjusted our CSS to increase the line-height and padding on all interactive list elements in the sidebar filters:

/* Mobile-Friendly Touch Targets */
 (max-width: 768px) {
    .filter-sidebar-list li a {
        padding: 12px 16px; /* Expanded tap target */
        display: block;
        font-size: 16px; /* Prevent automatic mobile safari zooming */
    }
    
    .quantity-selector button {
        width: 44px; /* Standard recommended touch size */
        height: 44px;
    }
}

I also ran through the shop using only my keyboard to test accessibility (using Tab and Enter keys to navigate). I made sure the dynamic search bar focus state was clear and that visually impaired users would have no trouble navigating the main shop menu. This step is incredibly important for modern search engines, as they place a heavy emphasis on real-world usability and accessibility metrics during their page experience evaluations.

---

Day 7: Launch, Benchmarks, and Final Reflection

On Sunday afternoon, it was time to run our final tests, launch the storefront, and compare our new setup against the old site. I moved the local files to the live production server, configured our SSL certificates, and pointed the client's domain to the new server IP.

I ran another mobile Lighthouse audit on the final live URL, and the results were beautiful. Our overall performance score jumped from sixty-nine on the raw template to ninety-eight on the customized build. The "First Contentful Paint" was clocked at just 0.8 seconds on a simulated mobile connection, and our "Cumulative Layout Shift" (CLS) was practically zero thanks to the rigid aspect ratios we applied to our image containers.

My client was thrilled. They tried loading the new store on their personal phone and could not believe how fast it popped up. Within forty-eight hours of launching, we noticed a positive trend in our Google analytics logs: our overall bounce rate plummeted to twenty-two percent, and the average page views per session jumped from two to nearly five pages.

Building this storefront showed me that you do not need to over-complicate your tech stack to get incredible results. By taking a clean, professionally designed design framework and combining it with modern build tools, a bit of custom vanilla script logic, and solid server-side rules, we built a highly competitive, lightning-fast digital medical shop that our clients love using. It was a week of hard work, but the incredible speed and clean user experience made every minute of tuning and tweaking completely worth it.

1 view

Add a comment

Replies

Be the first to comment