Bata Stanković PR
the website of an accounting agency from Lebane
An accounting agency sells nothing online. Its visitors want to see that the firm really exists, find a phone number and ask one question. The site was built around those three things, and then it turned out that its form hadn't sent a single message in months.
- Multi-page website
- Lebane
- August 2026
- 12 min read
- PHP 8.5
- Nginx with caching
- PHPMailer and SMTP
- Consent Mode v2
- Customer
- BATA STANKOVIĆ PR — Agencija za knjigovodstvo Lebane
- Industry
- Bookkeeping and accounting, activity code 6920
- Area
- Lebane, Leskovac, Bojnik, Medveđa, Jablanica District
- Type
- Site with a home page plus service, pricing and FAQ pages
- Technologies
- PHP 8.5 · Nginx cache · SMTP · Consent Mode v2
- Website
- batastankovic.com
- Status
- Live site, actively maintained

01 / 11
What the agency does
Bata Stanković PR is an accounting agency from Lebane. It was registered on November 4, 2014, under activity code 6920: accounting, bookkeeping and auditing activities. It works for sole proprietors, flat-rate taxpayers, craft workshops, shops, hospitality businesses and small companies in Lebane, Leskovac, Bojnik, Medveđa and the rest of the Jablanica District.
It is the kind of work nobody notices until something is late: the KPO ledger for flat-rate taxpayers, business books for sole proprietors, VAT records, payroll and employee registrations, tax returns, annual financial statements and their filing with the Serbian Business Registers Agency, and company registration with the APR. The full overview of services is on the site, and each major service has its own page with the details.
Nobody chooses an accountant by design. People choose based on whether they trust that deadlines will be met and whether they can get a person on the phone when an official notice arrives that they don't understand. That is why the site was built as proof that the agency exists, works and has a name, not as a brochure.

02 / 11
What needed solving
In the Jablanica District, almost no accounting agency has a proper website. They exist as a line in a directory filled in by a third party, with a phone number that may no longer work. That is an opportunity and an obligation at the same time: whoever shows up first with verifiable information is the one people remember.
The brief came down to four things:
- The phone number before anything else. Most visits come from phones, often in the middle of some deadline. The call button has to be within thumb's reach, on every page.
- Information that can be verified. Tax ID (PIB), company registration number, activity code, founding date and service area: that is what competitors in the directories don't have, and to someone handing over their paperwork it means more than any adjective.
- A form that actually delivers. A message sent from the site has to end up in the agency's inbox, and if sending fails, the visitor has to see it.
- Getting the agency found in search. First for Lebane, where the office is, and then for Leskovac, where most of the market is.
A fifth condition was never spoken but was taken for granted: the site must not carry a monthly platform fee and must not depend on plugins that update themselves and break by themselves.

03 / 11
Home page: how do you run your business
The home page doesn't start by listing services but with the question How do you run your business? Below it are four cards by client type (“I'm a flat-rate taxpayer”, “I keep books as a sole proprietor”, “I have an LLC” and “I'm just starting a company”), each with a short list and a link to its own page, plus the phone number and a note that the first consultation is free of charge.
Next come payroll and annual financial statements, a diagram of the paper trail (you: invoices and bank statements; us: books, filings and payroll; the state: the Tax Administration and the APR), an explanation of what the price depends on, the service area, details about the agency, a three-step agreement and an inquiry form. The menu leads to services, pricing and questions, and Contact remains an anchor link on the page.
04 / 11
A form that quietly didn't send for months
This is the finding that made the whole pass through the site worthwhile.
The stylesheet contained .custom-checkbox input[type="checkbox"] { display: none }. The field under that rule was required: consent to the terms of use. A visitor who didn't tick that box would click “Send message” and nothing would happen. No network request, no red outline, no message.
The reason lies in the browser's own rules: when validation fails, the browser tries to focus the field that failed it. A field with display: none can't receive focus, so the whole submission is aborted without a single visible sign. The console held exactly one sentence that nobody had ever seen, because nobody opens the console on someone else's website: An invalid form control with name=privacy is not focusable.
The fix was small. Since the redesign, the field is an ordinary visible checkbox styled in the theme color: it can still receive focus, and the sibling selectors that draw the nice-looking box keep working as before.
The lesson goes beyond this site and is worth remembering: a required field hidden with display: none is a silently broken submission. The site looks fine, the form looks fine, and the messages simply never arrive.
05 / 11
The path from form to inbox
Once the form was working, the next thing to look at was what happens after the click. The message goes to the server, then over SMTP to the agency's inbox. Along that path there were several more places where a lead could get lost.
The sender's address was being cleaned of dangerous characters before it was set as the reply-to address. For most people that changes nothing, but an address with an apostrophe in the name became invalid that way and killed the send before it even started: the visitor got “Sending failed” and the message vanished. Now the raw values are used, and escaping happens only when they are written into the message body.
The form protection was loaded with a check that failed in the wrong direction: if the protection file couldn't be read for any reason, the whole check was silently skipped, and the message went out anyway. Now such a case returns an error and the phone number instead of letting everything through.
On top of that: a request origin check existed in the code but nothing called it, so it was switched on; the header that allowed submissions from any site was removed; certificate verification on the connection to the mail server was restored; the service type is now chosen from a fixed list, because before that a bot could type arbitrary text into a field that ends up in the email; and a failed send now returns a real error instead of a silent confirmation.
Finally, the form also got the method and action attributes in the HTML itself, plus a fallback message with the phone number in case the script doesn't run. Without that, a script failure would have sent the name, phone and message into the address bar.
06 / 11
Claims the site must not make
Part of the work on a site like this isn't code but deleting. An accountant's website reads like a statement, so every sentence has to hold up to checking.
The list of services included auditing. In Serbia, audits may only be performed by a company licensed by the Ministry of Finance; a sole proprietor's agency with code 6920 is not allowed to offer them, however related it may sound. The card was replaced with annual financial statements, and auditing was removed from the form's dropdown, from the footer and from the data for search engines.
Elsewhere the site said “24/7 support”, while in the same file the opening hours given to search engines were 8 a.m. to 4 p.m. on weekdays. The two claims contradicted each other. What stayed is what is true.
The block with counters appeared twice on the same page. The second one was replaced by an “Agency details” section: full name, tax ID (PIB), company registration number, activity code with its full description, founding date and service area. It is the one part of the site the competition can't copy, because it isn't a matter of text but of the registry.
The legal texts existed in two versions that had drifted apart: one in pop-ups on the home page, the other as separate pages, with a last-updated date from the previous year. The pop-ups were deleted, the links lead to the real pages, and the terms page carries the correct date.
07 / 11
Speed and what was holding back the first screen
The site wasn't slow to open because of images or the server, but because of one rule in the script: the whole page was kept at zero opacity until everything had loaded, down to the last icon. So the largest element on the first screen couldn't appear before loading finished. The block was removed, and the time to the largest element dropped from 1045 to 448 milliseconds, measured in a real browser on desktop.
The script went from 31 kilobytes down to 20. A dead development URL that had been sitting in the public file was removed, two unthrottled scroll listeners were merged into one with deferred execution, and 215 lines of styles that the script injected after the main stylesheet were moved into the stylesheet, because in the old order they overrode the rule for visitors who had turned off animations in their system settings.
On the server side there was not a single rule for caching static files: styles, the script and icons were checked again on every visit. Now they carry a modification timestamp in the URL and are cached for a year, images for a month and the pages themselves for thirty minutes. A condition that disabled caching for all traffic with campaign tags in the URL was also fixed, which means for every visitor coming from an ad or an email.
After all that, measured on the live site: server response 76 ms, first paint 316 ms, largest element 448 ms, layout shift 0.0117. Zero console errors, zero code validation errors, no horizontal overflow at 390, 768 and 1440 pixels, and an A+ grade for the certificate.
08 / 11
SEO: diacritics, schema and what was wrong
The site carried a list of twenty-four keyword phrases in its head, with the word knjigovođa (accountant) repeated twelve times. Search engines have ignored that for fifteen years, but to anyone looking at the source code it says everything. Deleted.
The more serious problem was diacritics. In eighteen places (the title, the sharing data, the data for search engines and the visible text) it said “knjigovodja” instead of “knjigovođa”. That isn't cosmetic: to search they are two different words, and the agency wanted the correct one.
The page title was cut from 78 characters to 50, so the whole of it shows in search results. The sharing image returned a 404 error, so a new one was made at full size.
The data for search engines was the most interesting part. The type was correct (an accounting service), but the company name had been replaced with a keyword phrase, and the VAT number field held the company registration number, which are two different things. Now the name field carries the company's real name, the VAT number is the VAT number, the registration number sits in its own field, and service areas by city, opening hours by day, a logo and an image were added. A link that pointed to itself was also removed.
Finally: the robots rules had shrunk to three lines, so they were restored along with an explicit allowance for AI crawlers; a site description for language models was added; and the sitemap's URLs were aligned with the canonical form, because until then they differed by one slash.
09 / 11
When one page was no longer enough
Measurement confirmed what we had suspected: the site appeared for the query “accountant Lebane”, but not for “accounting services Lebane” or for anything related to Leskovac. One page can't rank for ten different intents, and Leskovac is a bigger part of the market than Lebane. The owner decided to go for all of it, including the price list.
The site grew from three URLs to twelve: a services overview, five separate pages by client type, a price list, a page for Leskovac and a questions and answers page. The shared frame was split into three files for the header, menu and footer, and each page only sets its own title, description, canonical URL and breadcrumbs, and the breadcrumbs turn into data for search engines automatically.
The writing rule was strict: every claim about a deadline, rate or threshold carries a comment in the code with the link to the regulation and the date it was checked. There are about ninety such comments, pointing to regulations, the Tax Administration, the APR and the e-invoicing portal. Content about tax deadlines without a source is a liability nobody wants.
The price list was built without a single made-up amount. The table explains what the price depends on (number of documents, VAT status, number of employees, line of business, foreign currency transactions), and every item says “on request” until the owner enters his prices. Until then, the site doesn't report prices to search engines either, because that would be a claim that doesn't hold.
On the questions and answers page there are twenty questions that clients actually ask. The text and the data for search engines are built from the same array in the code, so they can't drift apart, a common oversight on sites where the two are written separately.
The accounting in Leskovac page is deliberately not a fake city page: it says explicitly that the office is in Lebane and that Leskovac is part of the service area, not a second branch. A second address in the data for search engines would be a short-term gain and a long-term problem.
10 / 11
The clean URL trap
The new pages got clean URLs without the extension .php. The natural idea is to permanently redirect the old URL with the extension to the new one. That idea took the services overview down for several minutes.
The server internally rewrote the clean URL to the file with the extension, using a flag that restarts rule matching. Because of that, the rule that redirects URLs with the extension caught the server's own internal request: the page led to the file, the file back to the page, and so on in a loop until the server gave up.
The solution wasn't a smarter rule but no rule at all. The duplicate with the extension is handled by a canonical URL in the page head, and no link on the site points to the file with the extension, so in practice the duplicate doesn't even exist. If a redirect were ever really needed, the condition has to look at the URL the browser sent, not the one the server has rewritten in the meantime.
There is also a detail that can cost an hour of searching: since the pages have a thirty-minute cache, the cache has to be cleared after every change on the server. Otherwise you are testing the state from half an hour ago, and it looks as if the change wasn't applied.
11 / 11
What the client ends up with
A site the client owns. He pays for the domain and hosting, nothing else: no platform subscription, no plugin licenses, no monthly cost that grows when the site grows. The code and the content are in a repository and can be moved as a whole.
In practical terms: twelve URLs, each with its own title, description and canonical URL; phone numbers that dial with one tap from every page; a form that works and whose messages reach the agency's inbox instead of the void; company details that match the registry; and texts about regulations with a source behind every claim.
What was deliberately left to the owner, because it isn't a matter of code: the amounts in the price list. The table is ready and waiting for his figures. Until then it is honest for it to say “on request” rather than show a made-up figure someone would compare with a competitor's.
The site is still under active maintenance. Every change is made on the live site, with a backup before anything is touched and a check in a real browser afterward. For an accountant, deadlines are part of the job, and the same goes for whoever maintains the accountant's website.
On a phone
The same site, in your hand.
Most visitors come from phones, so the layout, buttons and images are arranged for the small screen first.

Questions
What clients ask.
Why does an accounting agency need a website when clients come through referrals?
A referral brings someone to a name, and they type that name into search before calling. If there is no website, they find a line in some directory filled in by a third party, with a phone number that may no longer work. The website is where the correct number, the opening hours and verifiable registration details live.
Why are there no amounts in the price list?
Because the owner hadn't set them when the site was built, and a made-up price on a website is worse than none. So the page explains what the price depends on (number of documents, VAT status, number of employees, line of business), and the amounts say “on request” until the real ones are entered. Until then, no prices are reported to search engines either.
Who writes the texts about tax deadlines and regulations?
The texts were written with a source for every claim about a deadline, rate or threshold: the page code holds a comment with the link to the regulation and the date it was checked. Regulations change, so the site itself says to check with the accountant before any decision, instead of acting as tax advice.
Can the owner edit the content on their own?
The site has no admin panel, because for this amount of content it would cost more than it would help. Changes are made directly in the files, with a backup before each one. If the content ever starts changing often, a panel can be added without rebuilding the site.
Why not WordPress?
Because for ten pages of text it would bring plugins that need updating, a theme that shifts under your feet and a monthly cost. The site is written in PHP with a shared frame for the header, menu and footer: a new page is one file, and nothing that wasn't written can break.
Does the site show up in search?
For the query “accountant Lebane” it does, as measured in August 2026. The Leskovac page was only created then, and local search takes months, not days. The next step that would bring the most isn't on the site but next to it: a business profile in search, which appears above the organic results.
Related projects
From the same field.

Halo City Leskovac
Tehnički Pregled Stanković 964
Ivković Prevoz