Website2026-08-21
Bata Stanković — an accounting practice website for Lebane and the surrounding area
The accounting agency does not sell anything over the Internet. Its visitor wants to see that the company really exists, find a phone number and ask a question. The site was built around those three things, and then it was discovered that the form on it did not send any messages for months.
- Client
- BATA STANKOVIĆ PR — Lebane Accounting Agency
- Activity
- Bookkeeping and accounting, code 6920
- Area
- Lebane, Leskovac, Bojnik, Medveđa, Jablanički district
- Type
- One-page site with additional search pages
- Technologies
- PHP 8.3 · Nginx cache · SMTP · Consent Mode v2
- Status
- Live site, under active maintenance
- Technology
- PHP 8.3 · Nginx sa keširanjem · PHPMailer i SMTP · Consent Mode v2
Problem and context
What does the agency do
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 entrepreneurs, lump sums, craft shops, shops, caterers and small businesses from the area of Lebane, Leskovac, Bojnik, Medveđa and the rest of the Jablanica district.
Business is one that is not seen until it is too late: KPO book for a flat-rate employee, business books for an entrepreneur, VAT records, calculation of wages and employee reports, tax returns, final account and submission to the Agency for Economic Registers, company registration in the APR. The entire overview of services is on the site, and each major service has its own page with details.
The client does not choose the accountant by design. He chooses based on whether he believes the deadlines will be met and whether he can get a person on the phone when he receives a solution he doesn't understand. The site was therefore created as proof that the agency exists, works and has a name, and not as a brochure.
What needed to be solved
In Jablanički district, almost no accounting agency has a real website. They exist as a line in a catalog filled in by someone else, with a phone number that may no longer be valid. It is both an opportunity and an obligation: the first to stop with verifiable data is the one who is remembered.
The task was reduced to four things:
- Phone number before anything else. The majority of visits come on a phone, often in the middle of a deadline. The invitation must be within thumb's reach, on each page.
- Data that can be checked. PIB, registration number, activity code, date of establishment and area of work — this is what the competition does not have in the catalogs, and it means more to the person who submits his papers than any adjective.
- A form that actually arrives. The message sent from the site must end up in the agency's inbox, and if the sending fails, the visitor must see it.
- For the agency to be found in the search. First for Lebane, where the office is, and then for Leskovac, where the largest part of the market is.
The fifth condition was unspoken but implied: the site must not incur a monthly platform fee and must not depend on self-updating and self-breaking plugins.
Title: all on one scroll
The site is essentially one page. The menu does not lead to other addresses but moves the page to its own section, with an offset of eighty pixels so that the title does not end up below the fixed bar. The order of the sections follows the order of the questions the visitor asks.
The first screen has the name of the agency, a sentence about what it does and two buttons: a phone number and a free consultation. On the right side stands a digitron that adds up in dinars, with cards for invoices, bills, reports and calculations — the only decoration on the site, and it is such that it immediately tells what it is about.
Below are six services with listed items in each, then six reasons for cooperation, then counters with year of establishment, number of years of work, working hours and activity code. Then comes the block about the agency with full registration data, then contact form, phone, e-mail address and working hours.
Across the page, in the lower right corner, there is a floating call button. It's not original, but it's the only element that works the same at every scroll position: a person who has already decided doesn't have to search back to the top.
A form that was quietly not sent for months
This is the find that made the whole passage through the site make sense.
In the styles it said .custom-checkbox input[type="checkbox"] { display: none }. The field under that rule was mandatory — agreement to the terms of use. A visitor who would not tick that box would click on "Send message" and nothing would happen. No network request, no red box, no message.
The reason is in the rules of the browser itself: when the validation fails, the browser tries to focus the field that collapsed the form. A field with display: none cannot be focused, so the entire sending process is aborted without any visible consequences. There was exactly one sentence in the console that no one had ever seen, because no one opens the console on someone else's website: An invalid form control with name=privacy is not focusable.
The repair was small. The box stays in the flow of the page, just invisible — position: absolute, opacity: 0, twenty by twenty pixels — so it can still focus, and the neighboring selectors that draw the pretty house continue to work as before.
The lesson is more general than this site, and worth remembering: a required field hidden with display: none is a silent submission failure. The site looks right, the form looks right, and the messages just don't arrive.
Path from form to inbox
When the form worked, it was the turn of what happens after the click. The message goes to the server, then via SMTP to the agency's mailbox. On that road, there were several other places where the lead could be lost.
The sender address was cleaned of dangerous characters before being entered as a reply-to address. For most people, this doesn't change anything, but an address with an apostrophe in the name becomes invalid and crashes the send before it even starts — the visitor gets a "Sending Error" and the message disappears. Now the raw values are processed, and the cleaning is done only when printing in the body of the message.
Form protection was loaded with a check that relaxed in the wrong direction: if the file with the protection was not readable for any reason, the entire check was silently skipped and the message went through anyway. Now such a case returns an error and the phone instead of missing everything.
Also: the request origin check existed in the code but was not called by anyone, so it was turned on; removed the header that allowed sending from any site; the certificate check on the connection to the mail was returned; the type of service is now selected from a closed list, because before that the bot could write arbitrary text in the field that ends in an email; and sending failure returns a real error instead of a silent confirmation.
In the end, the form got both method and action in the HTML itself, plus a backup printout with a phone number in case the script doesn't work. Without it, dropping the script would send the name, phone number and message to the address line.
Claims that the site must not carry
Part of the work on a site like this is not code, but deletion. The bookkeeper's website reads like a statement, so every sentence must stand up to scrutiny.
Among the services was revision. In Serbia, only a company with a permit from the Ministry of Finance can perform an audit — an entrepreneurial agency with code 6920 cannot offer that, no matter how similar it sounds. The tab has been replaced with a final account, and the revision has been removed from the drop-down list in the form, from the footer, and from the search engine data.
Elsewhere it said "24/7 support", and in the same file the search engine hours were eight to sixteen, weekdays. The two claims were viewed side by side. The rest is what is true.
The counter block stood twice on the same page. The second one has been replaced by the section "Data about the agency": full name, TIN, registration number, activity code with full description, date of establishment and area of work. It is the only part of the site that the competition cannot copy, because it is not a matter of the text but of the register.
The legal texts existed in two versions that diverged — one in pop-ups inside the homepage, the other as separate pages, with a modification date from the previous year. The windows are cleared, the links go to the right pages, and the terms page has the correct date.
Performance and what was holding back the first screen
The site was not opening slowly because of the images or because of the server, but because of one rule in the script: the whole page was kept at zero transparency until everything was loaded, up to the last icon. The largest element of the first screen could therefore not appear before the end of loading. The block was removed, and time to largest element dropped from 1045 to 448 milliseconds, measured in a real desktop browser.
The script was reduced from 31 kilobytes to 20. A dead address from development that was in the public file was removed from it, two unmuted scroll listeners were reduced to one with delayed execution, and 215 lines of styles that were inserted from the script after the main file were moved to styles — because in the old layout they violated the rule for visitors who turned off animations in the system.
There were no static caching rules on the server side: styles, script and icons were checked on every visit. Now they carry a timestamp of changes in the address and are cached for a year, images for a month, and the pages themselves for thirty minutes. Additionally, a condition that excluded the cache was fixed for all traffic with campaign tags in the address — that is, for any visitor coming from an ad or email.
After all, measured on a live site: server response 76ms, first print 316ms, largest element 448ms, content offset 0.0117. Zero console errors, zero code check errors, no horizontal overflow at 390, 768, and 1440 pixels, A+ for certification.
SEO: Diacritics, Schema, and What Was Wrong
The site carried a list of twenty-four keyword phrases in its header, in which the word accountant was repeated twelve times. This has been ignored by search engines since fifteen years ago, but to a person looking at the source code it says everything. Deleted.
A more serious problem was diacritics. Eighteen places — in the headline, in the share data, in the search engine data, and in the visible text — read "bookkeeper" instead of "bookkeeper." It's not cosmetics: those are two different search words, and the agency wanted the right one.
The title of the page has been reduced from 78 characters to 50, so that the entire page can be seen in the search result. The image to share was returning a 404 error, so a new one was created in full format.
The search engine data was the most interesting. The type was correct — a bookkeeping service — but the company name was replaced by a key phrase, and the VAT number field was filled with a registration number, which are two different things. Now the name bears the real name of the company, the VAT number is the VAT number, the registration number is in its own field, and the area of operation by city, working hours by day, logo and picture have been added. The link that pointed to itself was also removed.
In the end: the robots.txt directives had fallen to three lines, so they were brought back together with an express permission for artificial intelligence; added site description for language models; the sitemap has aligned the addresses with the canonical form, because until then it differed by one slash.
When one panel became too narrow
The measurement showed what was suspected: for the query "bookkeeper of Lebane" the site appeared, but for "bookkeeping of Lebane service" and for anything related to Leskovac it did not. One side cannot rank for ten different intentions, and Leskovac is a bigger part of the market than Lebane. The owner decided to go with everything, including the price list.
The site grew from three addresses to twelve: overview of services, five special pages by type of client, price list, a page for Leskovac and a page with questions and answers. The common framework is separated into three files for header, menu, and footer, and each page specifies only its own title, description, canonical URL, and snippets — and the snippets themselves are converted into data for browsers.
The rule of writing was strict: every assertion about a deadline, rate or limit carries a comment in the code with the address of the regulation and the date of verification. There are about ninety such comments, and they lead to regulations, the tax administration, APR and the portal of electronic invoices. Unsourced tax deadline content is a liability no one wants.
The price list is made without any fictitious amounts. The table explains what the price depends on — the number of documents, the VAT system, the number of employees, activity, foreign exchange operations — and each item is "on request" until the owner enters his prices. Until they are gone, the site does not report prices to search engines either, because that would be an untrue claim.
On the page questions and answers there are twenty questions that clients actually ask. Text and browser data are built from the same string in the code, so they can't diverge — a common mistake on sites where the two are written separately.
The page knjigovodstvo u Leskovac is intentionally not a fake city page: it explicitly says that the office is in Leban, and Leskovac is the area of work, not another office. Another address in search engine data would be a short-term gain and a long-term problem.
The clean-URL redirect trap
New pages were given clean URLs without the .php extension. The obvious approach is to permanently redirect the old URL with the extension to the new one. That approach took the service page offline for a few minutes.
The server internally rewrote the clean URL to a file with an extension, with a flag that restarts location matching. Therefore, the rule that redirects URLs with the extension was capturing its own internal request: the page pointed to the file, the file back to the page, and so on until the server gave up.
The safest solution was to remove that redirect rule. A duplicate with an extension is resolved by the canonical URL in the page header, and no link on the site leads to a file with an extension, so in practice the duplicate does not even exist. If redirection is still necessary, the condition must look at the original URL requested by the browser, not the one the server has rewritten itself in the meantime.
There is also one easy-to-miss operational detail: since the pages have a thirty-minute cache, after each change on the server the cache must be cleared. Otherwise, the state from half an hour ago is tested and it seems as if the change was not applied.
Outcome
What the client received
A site the client owns. You pay for the domain and hosting, nothing else — no platform subscriptions, no add-on licenses, no monthly fees that grow as the site grows. The code and content resides in the repository and can be moved in its entirety.
Practical: twelve URLs, each with its own title, description and canonical URL; one touch phone numbers on each page; a form that works and whose message arrives in the agency's inbox, not empty; company data matching the register; and regulatory texts that have a source behind each claim.
What is deliberately left to the owner, because it is not a matter of the code: the amounts in the price list. The table is ready and waiting for his figures - until then it is fair to write "on request", and not a fictional figure that someone would compare with someone else's.
The site is still under active maintenance. Every change goes to the live site, backed up before anything is touched, and verified in the right browser afterwards. With an accountant, the deadline is part of the job — the same applies to the person who maintains his website.
FAQ
Frequently asked questions
Why should an accounting agency have a site, when clients come by referral?
The recommendation leads the person to the name, and he types that name into the search before calling. If there is no site, he finds a line in a catalog filled by someone else, with a phone number that may no longer be valid. The site is a place where the correct number, working hours and registration data can be verified.
Why is there no amount in the price list?
Because the owner did not specify them at the time of creation, and the fictional price on the site is worse than none. The page therefore explains what the price depends on — the number of documents, the VAT system, the number of employees, the activity — and the amounts are "on request" until the correct ones are entered. Until they are, prices are not reported to search engines either.
Who writes texts about tax deadlines and regulations?
Texts are written with a source for each claim about a deadline, rate or limit — in the code of the page there is a comment with the address of the regulation and the date it was checked. The regulations are changing, so the site itself says to check with an accountant before any decision, instead of acting as a tax advisor.
Can the owner change the content himself?
The site does not have an admin panel, because for this volume of content it would cost more than it would use. Changes go directly to the file, with a backup before each one. If the content changes frequently one day, the panel is added without rebuilding the site.
Why not WordPress?
Because for ten pages of text it would bring plugins that need to be updated, a theme that changes underfoot and a monthly cost. The site is written in PHP with a common framework for the header, menu and footer — the new page is a single file, and nothing unwritten can even break.
Does the site show up in search?
For the query "bookkeeper of Lebane" it appears, measured in August 2026. The page for Leskovac was created only then, and a local search is a job of several months, not a few days. The next step that carries the most weight is not on the site, but next to it — a business profile in the search, which is above the organic results.
Next step
Have a project that needs to work better?
Send me your goal and current situation. I will reply with a concrete scope proposal.
Start a project