Case study · PHP 8 · MySQL · Performance · Education
Making a coaching website fast by construction, and measuring it honestly.
Diksha Learning Center is a coaching institute in Chamba. This is how its website was built to load quickly on a phone, how a single photo upload becomes six optimised images, what a live Lighthouse run found, and the checkout I built and then switched off.
The problem
A coaching site has to be quick on a phone, findable, and editable by the people who run it.
Diksha Learning Center in Chamba teaches NEET, JEE, NDA and state government-exam batches. Its audience is students and parents searching on phones, often on mobile data, comparing a few institutes in one sitting. A slow page loses them before they read a word, and a site that can only be changed by calling a developer is wrong within a year, because batches, fees and teachers change every year.
The brief that follows from that has three parts: fast on a modest phone, easy for Google to understand, and fully editable by the institute. Everything below is a consequence of those three.
Speed, part one: images
Photos are the heaviest thing on any coaching site, so the site handles them for you.
The common failure is someone uploading a 4 MB phone photo of a classroom into a slot that shows it at 400 pixels wide. So the admin never stores the original. On upload, the site scales the photo down if it is very large, then makes three copies at 300, 800 and 1200 pixels wide, each saved as WebP at quality 85, 80 and 70, and, when the server's PHP can do it, also as AVIF at 70, 65 and 60. Smaller copies get lower quality because the eye can't tell.
On the page, one helper writes a picture tag: AVIF sources first, then WebP, then a plain WebP img, each with the three widths and a sizes hint. A phone downloads the 300 or 800 pixel file in the newest format it understands. The first image on a page is marked high-priority and eager, everything else is lazy-loaded, and every image carries a width and height so nothing jumps. In the live test, each home-page course photo came to between 26 and 53 KB.
A weakness I found reading this back. The AVIF files are only made if the server has AVIF support, but the picture tag always lists AVIF first. On a host without it, a browser would pick the AVIF address, get nothing, and not fall back to WebP, so the pictures would be missing. It works on the live host because that host can make AVIF. The fix is to list a source only if its file exists.
Speed, part two: very little code
The fastest script is the one you don't ship.
The public site doesn't use Bootstrap, jQuery or any framework. It has five stylesheets, about 40 KB written and 9.5 KB over the wire, and one script of 2.9 KB, less than 1 KB compressed, that opens the mobile menu, makes the header stick and turns the hero slides every six seconds. The pages are built on the server in plain PHP, so the phone receives finished HTML (9.3 KB compressed for the home page) and has nothing to assemble.
The result is the number that surprised me most: the site's own code, HTML included, is about 10 KB over the wire. The parts that make the page heavy are the ones it doesn't own, which is the next section.
Speed, part three: telling the browser what to keep
A year for files, never for pages.
The .htaccess file switches on compression, and asks the browser to keep images, styles, scripts and fonts for a year, while sending pages with no-store so a changed fee appears immediately. It also adds security headers and hides folder listings.
There's a trade-off here that is easy to miss. The stylesheets keep the same file names, so a visitor who already has sections.css keeps the old one for up to a year, even after I change it. The usual fix is to add a version to the file name when it changes. I haven't done that, so a design change today would not reach returning visitors until I do.
Measuring it
Lighthouse on the live site, mobile preset.
On 3 October 2026 I ran Lighthouse 13.5 against the live site with the mobile preset, which simulates a mid-range phone on slow mobile data. It scored 91 for performance, 96 for accessibility, 92 for best practices and 100 for SEO. The server responded in 130 ms, blocking time was 20 ms and layout shift was 0.01.
The slow numbers were first paint at 2.6 seconds and the largest image at 2.9 seconds. The test loaded 47 requests and 1.8 MB, and almost none of it is the site's own code. It's a YouTube video on the home page that loads with the page (about a megabyte on its own, with its player and scripts), an icon font of roughly 320 KB, Google Fonts, and two small tracking scripts the host adds. So the next two changes are obvious: load the video only when it's tapped, and host the fonts on the site. I'm reporting the 91 as it is rather than rounding it up, and one Lighthouse run moves by a few points from run to run.
The checkout I built and then switched off
A payment flow where the browser never decides the price.
The site has a complete Razorpay flow. The student never sends a price: the server looks up the course fee in its own database, turns it into paise, creates a Razorpay order and saves an enrollment row marked "created" along with the visitor's IP address and browser. After the student pays in Razorpay's own window, the server verifies Razorpay's signature before it marks the row "paid", and refuses to process one that's already paid. A bad signature marks it "failed".
Then the button was hidden. On the public course pages the Enroll Now link is commented out, and students send an enquiry instead, which fits how a small institute actually admits students. So this page doesn't say that real money moves through the site, because right now it doesn't. Two things stand between this and live: the keys in my working copy are Razorpay test keys, written into the code instead of a private settings file, and there is no webhook, so a student who pays and closes the tab straight away would stay "created" until someone checks the dashboard.
The enquiry form
Spam protection that doesn't make a student solve a puzzle.
A CAPTCHA is a tax on exactly the person the institute wants to hear from. So the form uses four quiet checks instead: a hidden field that humans leave empty and bots fill in, a security token tied to the visitor's session, a minimum time between loading the form and sending it, and a ten-second gap between two sends. A valid enquiry is saved with the course it was about, the page it came from, the phone number tidied to ten digits, and the visitor's IP and browser.
The honest gap: the enquiry is saved to the database and nothing else. The email service is switched off in the configuration, so there's no alert, and someone has to open the admin panel to see a new lead. For an admissions business that's the next thing worth fixing.
Roles, permissions and a shared foundation
The admin panel is the same bones as my other client sites.
The admin has several accounts, each with a role, and each role gets permissions per section: view, add, edit and delete, separately for courses, blogs, enquiries and the rest. Every admin page asks for its permission before it shows anything, so a front-desk login can read enquiries and can't change a fee. The lists in the admin are loaded through small AJAX endpoints, one per screen.
The foundation under it (the bootstrap file, the database and query helpers, the admin shell, the roles system and the image pipeline) is the one I use on other client sites such as Wander Yak, and the two share the same folder layout. I didn't turn it into a generic framework. It's two sites with the same bones, each with its own courses, bookings and design.
The architecture
What's actually running under it.
- Backend
- Plain
PHPwith a small kernel: a database class using prepared statements, a query and helper class (image pipeline, formatting, redirects, permissions) and an SEO manager. Models for courses, faculty, reviews, blogs, sliders and statistics. - Database
MySQL, tables created by a migration script, with a seeding script for sample courses, faculty, reviews, sliders and blogs.- Front end
- Server-rendered pages, five hand-written stylesheets and one 2.9 KB script. No framework and no build step.
- Routing
- Clean addresses through
.htaccessrewrites, for the public site and for every admin screen. - SEO
- A manager that writes titles, descriptions, canonical links and social tags, plus Organization, Course, BlogPosting and FAQ structured data, a sitemap and an RSS feed.
- Payments
Razorpaythrough Composer: order creation, a checkout window and signature verification, currently switched off in the public pages.- Admin
- A Bootstrap-based panel with permission checks on every page, searchable lists loaded over AJAX, and Chart.js dashboard charts.
- Size
- About 3,900 lines of PHP across the public pages, and roughly fifty admin screens.
- Verification
- No automated tests. Checked by using it, and by the Lighthouse run above.
The outcome
Honest about where it actually is.
The institute has a site that scores 91 on mobile performance and 100 for SEO, with about 10 KB of its own code, and that its own staff can run. Courses, teachers, slides, reviews, blogs and every policy page are theirs to edit, and admin roles keep the wrong person away from the wrong screen.
What it isn't yet: a site that takes payments, a site that emails when an enquiry arrives, or one that scores 100. Those are three small, known tasks, and they're written above instead of left for a visitor to discover.
Running a coaching institute, school or training business, and want a site that's quick and yours to edit?
Get in touch