Website2026-08-21
Denis Market 97 — a website for a local store and phyllo pastry production
The same company sells detergent to a neighbor from the village and thin crusts to a bakery in Belgrade. It was necessary to create one site that takes both of them where they need to go, without mixing messages and without any page that exists only to be counted by the search engine.
- Client
- Denis Market 97, Nikolinci
- Activity
- Market of mixed goods and pie crust production
- Area
- Alibunar municipality · Belgrade
- Type
- Multi-page site, 37 pages
- Technologies
- PHP 8.3 · own router · no database · JSON-LD
- Site
- denismarket.rs
- Status
- Live site, under active maintenance
- Technology
- PHP 8.3 · Sopstveni ruter · Bez baze i bez CMS-a · JSON-LD @graph
Problem and context
What does Denis Market 97 do
Denis Market 97 is a small company from Nikolini that does two jobs under the same name. The first is the market on Glavna 49, in the center of the village in the municipality of Alibunar, open every day from 6 a.m. to 11 p.m., and on weekends and holidays. The second is the production of thin crusts for pies and gibanica in Ledine, Surčinska, for bakeries, restaurants, markets and households.
The shop was opened in 2025, with an idea that everyone in the village understands: to have everything and not to close at seven in the evening. Goods arrive daily, bread and dairy products in the morning, shelves are replenished during the day. On the shelf are food, cured meats and delicacies, household chemicals, sweets and snacks, drinks, as well as bakery and ready-made products.
The second part of the job has a completely different audience. The crusts are sold to the bakery that bakes the pies every morning and to the restaurant that has them on the menu, and that conversation is about the quantity, about the delivery dynamics and about making sure that the crust is the same on Monday and Saturday. The same company, the same phone number, but a customer who in no way resembles the neighbor from Nikolinac.
Behind both jobs is one man who answers the phone for both the shop and the peels. This was also the starting point for the entire project: everything the site does, it must do without a central office, without a ticket and without someone sitting at the panel.
Two jobs, one name, one site
The first conversation opened the question that determined everything else: two sites or one. Two sites means two domains, two hostings, two SEO jobs and two places that someone has to maintain, for a one-man company. One site carries another risk: that the bakery from Belgrade lands on the page about detergents, and the neighbor from the village on the page about wholesale.
The solution is one brand with two separate branches. The title carries both and separates them immediately, in two buttons below the title. After that, the paths do not mix: everything about the shop is under /market/, everything about the skins under /kore/, and only what is really common remains in common — contact, about us, locations and FAQ.
The second decision was that the site should not be a store. No basket, no prices, no checkout. The prices in the store are on the shelf and change more often than the website would be updated, and the price of the crust depends on the quantity and dynamics, so it is agreed upon in conversation. The site has one task: to bring a person to the door of a shop or to a call about barks, with all the information he needs for that.
That decision freed the entire project from one big obligation. A site without prices does not become outdated when prices change, and for a country store this means a site that is accurate for a year.
Structure: 37 pages that were not created all at once
The first version had seven pages: homepage, market, phyllo pastry, About, contact and two legal pages. When the owner reviewed the first version, he asked for more pages and more content. The request was good, because when writing it, it turned out that each group of goods and each type of customer has its own question, and requires a different explanation.
Today the sitemap lists 37 addresses in five branches:
- Market — overview page, opening hours and six pages by product group: food, delicatessen, household chemicals, sweets and snacks, beverages, bakery and ready meals.
- Crusts — overview page and nine subpages: for pies, for bakeries, for bakers, for caterers, for markets, for households, wholesale, how they are made, storage and use.
- Recipes — overview page and six recipes: gibanica, širnica, zeljanica, krompiruša, baklava and strudel with apples.
- Locations — an overview page with a table of distances, then Nikolinci and Ledine, then three surrounding towns.
- Common — about us, contact, FAQ, privacy policy and terms of use.
None of those pages were created from a template with a replaced name. Each has its own text, its own title, its own description and its own FAQ. A page created only to increase the page count by the search engine brings neither visitors nor ranking, and carries a risk for the entire domain.
The depth remained shallow: each page can be reached from the homepage in a maximum of two clicks, because the footer lists everything. The sitemap is written from the same data used to render the pages, so a new page cannot exist without also appearing in the sitemap.
Market: from the shelf to working hours
The page Market in Nikolinci does what a good seller would do: he says what he has, how it works and where it is. The offer is divided into six groups, each with its own side, and on it examples of representative products.
What is deliberately absent on those pages are prices, weights and brand names. Instead, there is a sentence that is both correct and useful: the prices are in the store, on the shelf, and if something is not there, say so and it will be ordered for the next tour. It is both a description of the service and an invitation to talk, and it never gets old.
Working time got its side. That's not an exaggeration: business hours are the only information that makes someone pull their phone out of their pocket at half past ten in the evening, and the only information that needs to be visible before that person gets into the car. On the page there is a schedule for all seven days, a sentence about holidays and a note that the bark plant works by appointment, on weekdays.
The same working hours are also written in structured data, through openingHoursSpecification for all seven days. This is what allows the search engine to print "open" or "closed" next to the results at that moment, without entering the site.
Core: From Product to Business
The bark branch was the hard part. The product is simple, the business is not. Crusts for pie and pastry have nine subpages, and they are divided into two types. One describes the product: pie crust, pastry crust, how they are made, storage and use. Others describe the customer: bakeries and pastry shops, restaurants and catering, markets, households.
The division has a reason. The bakery and the household demand different things from the same crust. It is important for the bakery that there is no shortage in the morning and that the quantity is agreed in advance; it is important to the household how much crust goes into the pan and how to keep it from drying out while the pie is being folded. One page trying to serve both audiences convinces neither.
The tenth page was created later, from the hole revealed by the comparison of the content with what is actually typed into the search: the word wholesale did not exist anywhere on the site, which is the umbrella term under which this business is sought. The existing pages explained to whom it was sold. The new page wholesale kora tells how the deal goes: what to say in the first call, what the test tour looks like, how dynamics and pickup are agreed upon, and why the prices are not on the website. The text answers a different question from the other pages about the barks, so it is not repeated anywhere.
Crusts, on the other hand, are also the biggest part of the job in which it is easy to overdo it. Everything that is claimed about them on the website could be verified with the owner: the process, purpose, way of packaging, what they are made of. What could not be checked — weights, deadlines and prices — was not written, and the page is not weaker because of that.
Recipes as an entry point for people who are not yet looking for crusts
The six recipes on the site have a reason. A man looking for a "crust pastry" isn't looking for a supplier — he's making pie on Sunday morning. But he is also the only reason why someone outside the profession would ever hear about the plant in Ledine, and among those people are those who run a bar or make pies for fame.
That's why recipes with ready-made crusts carry gibanica, cheese, zeljanica, krompiruša, baklava and strudel with apples. Each one has ingredients for one casserole, step-by-step instructions, tips and questions, and each one works without the Ledino crust. The recipe, which is actually an advertisement, is recognized from the first and is not shared further.
Technically, each recipe carries Recipe data with steps like HowToStep, preparation time, baking time and number of servings. It is one of the few types of results where the search engine still displays the image and preparation time directly in the list.
A small detail that is easy to overlook: the author of the recipe must be a clear reference to the company via @id, without repeated @type and name. If the type is repeated, joining by @id adds a third type to the firm in the graph that does not belong there, and the entity becomes something it is not.
Locations: distances that are measured, not estimated
Nikolinci has about 990 inhabitants according to the 2022 census. A site that counts only on that village has an audience that is difficult to measure, so the location branch also covers the surrounding places from which the store is visited. But not all, and that was the most difficult decision on the project.
Eight sides of nearby locations were researched. Three were built: Banatski Karlovac, Uljma and Alibunar. The reason is simple. For a place more than seventeen kilometers away, there is not a single honest sentence that would fill that page — the store is in Nikolinci, it does not deliver, and it has nothing special to offer that particular place. Eight almost identical "marketplace for buyers from X" pages is a pattern that search engines recognize as a doorway, and the consequences fall not on those pages but on the entire domain. The other places got a row in the table of distances on the overview page, where they belong.
Each page of the site carries the question "Do you deliver to X?" and the answer is "No". Better for the visitor to find out on the website than after driving. Neither of those sides has Place in the structured data, because Place would claim that there is an object there, when there isn't.
Distances in the text are measured, not estimated: addresses through Nominatim, route through OSRM, so the road mileage and driving time are included in the text. The difference is felt. Belgrade is about 55 km by air, and 63.8 km by road; the site that writes the first number turns out to be incorrect to anyone who has driven that ride. The same applies to nearby towns: Banatski Karlovac 4.2 km, Uljma 7.5 km, Alibunar 9.8 km, Vršac 21.7 km.
In addition, an administrative trifle that is easy to make a mistake was checked: Uljma does not belong to the municipality of Alibunar, but to the city of Vršac. It's one sentence on the site, but it's the difference between a site that knows the end and a site that rewrites it from someone else's side.
Technical performance
The site is written in PHP 8.3, with its own router in index.php. No WordPress, no plugins and no database. All the content — product groups, skin subpages, recipes, locations, FAQs, and legal texts — resides in plain PHP files in data/, and is rendered by views.
Why no database: a site with 37 pages and content that changes several times a year has nothing to gain from MySQL except one more service to go down and one more backup to make. This way, the entire content is text that can be opened and read, and moving to another server is copying a folder. If the scope changes one day, the database is added under the same data layer.
The addresses are clean and with a trailing slash: /market/offer/pekara/, /kore/za-pekare/, /recepti/gibanica/. The router breaks them down into sections and subpages and selects the appropriate view. The unslashed versions are redirected, and redirects from http, from www and from the old test subdomain preserve the path, so no split link ends up on the home page.
The site has a day and night theme, derived via CSS tokens, with a transition that does not flicker when changing. Titles go in Fraunces font, text in Manrope, both from own server, no call to Google Fonts. One detail that is often missed on the Serbian site: the letters č, ć, š, ž and đ are not in the basic Latin font, but in the extended one, so the preload must cover both files — otherwise the entire text is redone once as soon as the right font arrives.
The contact form is the same for both branches, with a choice of inquiry topic: a question for the shop, an inquiry about skins or something else. Instead of CAPTCHA, there is spam scoring with hidden bait, because CAPTCHA on the site of the village shop is rejected by more real people than robots. Every inquiry goes to the log, both the one that passes and the one that the filter rejects, so that no message disappears without a trace.
Speed, animations and seeing the site no matter what
Measured on PageSpeed Insights: performance 98 on mobile and 100 on desktop, CLS 0, TBT 0, SEO 100. Behind it is a collection of little things: images in WebP format with srcset and correct sizes, width and height on each image so the layout doesn't move while loading, a preload image hero that also carries an imagesrcset (without it the preload pulls the wrong size and the work is wasted), and static with a long cache and version in the name.
After the first version, the owner asked for more movement on the site. It's a request that easily defeats the measurement, so it's done carefully. The curtain with the number 97 only goes on the first visit and is remembered in the session. Sections are revealed on scroll, images have parallax linked to scroll, transition between pages slides. All animations work on layers that do not touch the layout, so after them the measurement did not fail.
One thing is regularly overlooked on sites like this: if content is hidden until JavaScript discovers it, and JavaScript doesn't arrive for whatever reason, the visitor gets a white page. That's why there is a short guard in the header that removes that hiding after two and a half seconds if the script has not responded. In that case, the site is seen without animations, which is the only acceptable outcome.
Statics is served with a thirty-day cache, so every style or script change carries a new version in the address. Without it, the change is visible only to the first time visitor, and the owner looks at the old site and asks why nothing has changed.
Structured data, meta and visibility
JSON-LD is written as one @graph, not as a bunch of independent blocks. The company is GroceryStore with a full legal name, personal data, address, working hours for all seven days and a list of places where customers come from. The facility in Ledine is a department with its address and coordinates, linked to the company via @id. Kors are one entity with the same @id on all pages where they are mentioned, so they do not appear in the graph as three different products with the same name.
One item was intentionally left incomplete. Product has no offers, because the prices depend on the quantity and are not public. The checker therefore reports a warning permanently, and this is accepted knowingly: a Offer without a price gives the same warning, and a fictional price would be worse than a warning. When the client chooses to publish prices, the warning disappears automatically.
Titles and descriptions are measured in pixels, not characters. A 158-character description is truncated in the result if the words are wide, and a longer description with narrow letters fits in its entirety. After the measurement, no title or description is cut on the site.
The rest is the crafting part: canonicals pointing to themselves, real 404 without canonical to title, lastmod in sitemap by source file of page instead of common date for all, and robots.txt naming AI robots with repeated rules — the named group completely tramples the general one, so the rule must be repeated to be valid.
The domain has been verified in Google Search Console as a domain property and the sitemap has been submitted. Search engines are notified via IndexNow, with the key published on the domain; all addresses were reported on the same day the site was launched, so indexing did not wait for a robot to accidentally stumble upon it.
Outcome
What the client received
A site on its own domain that does two jobs without interfering. The neighbor from Nikolinac finds the working hours, address and what is on the shelf. The bakery from Belgrade finds a page about wholesale, a description of the procedure and the number to call. The inquiry from the form is sent to the e-mail, without a panel to log in and without a monthly subscription to anyone.
No WordPress asking for an update every other week and no plugins that break on the next version. The content is in files, the site remains portable, and every change goes through a script that first makes a backup copy, then checks the syntax, and only then delivers it.
What remains is what the site can't do for the owner: photos from the shop and from the plant, the company's Google profile for both locations, and the bark prices if one day they become public. All three are written down and waiting for him.
What is already certain: before this site, the company was only found through aggregators, on other people's pages with other people's schedule and other people's data. Now there is one place where it says what is correct, and that place is his.
FAQ
Frequently asked questions
Why one site for two jobs, and not two separate sites?
Because of who runs them. Two sites mean two domains, two hosting and two places to maintain, and behind both jobs there is one man. One site with two separate branches gives the same result: the paths don't mix, and the brand and phone are the same, just like in reality.
Why doesn't the site have a price or cart?
Because both would be incorrect. The prices in the store are on the shelf and change more often than the website would be updated, and the price of the bark depends on the quantity and dynamics of delivery, so it is negotiated during the conversation. The site instead leads to an action or a call.
Can a new page or a new group of goods be added?
It can, and without touching the code. The content is in separate files, so a new group of goods or a new subpage about phyllo pastry is added as an entry. The sitemap is written from the same data, so the new page cannot be left out of it.
Why no WordPress and no database?
For a site of this scale, the database and CMS bring more obligations than benefits: updates, plugins and another service that can crash. This way all the content is text in files, the site is transferred by copying folders, and there is nothing that requires maintenance every other week.
Why weren't pages made for all the surrounding sites?
Because five of them would be empty. For a place more than seventeen kilometers away, there is no honest claim to fill the page, and a series of almost identical pages are recognized by search engines as doorways, and the risk affects the entire domain, not just those pages. Those places are included in the distance table.
How long does it take to create a site like this?
Depends on the number of pages and how much content needs to be written from scratch. The code is a minor part of the work: the texts, checking the claims with the owner and preparing the images take the most time. This site grew in steps, from seven pages to thirty-seven.
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