Skip to content

Commit a836c61

Browse files
committed
Compress what is served, and tell Google what this business is
Two things: the site got a lot faster, and the block of structured data every page carries now describes an actual business rather than a name and a phone number. --- speed Nothing was compressed. That did not matter much while every page was an empty <div id="root">; it matters a great deal now that each one carries its own markup, because the home page went from 3 KB to 74 KB in the process. Gzipped it is 13 KB. Brotli first where the host has it, gzip behind it, and images and fonts deliberately left alone -- WebP, AVIF, JPEG and WOFF2 are already compressed and running them through again spends CPU to make the file very slightly larger. Measured on a throttled phone -- 390px, 1.6 Mbps, 150 ms latency, 4x CPU: first paint largest paint / 1484 -> 692 ms 2124 -> 1344 ms /cars 1108 -> 652 ms 1356 -> 908 ms /tariff 1108 -> 640 ms 1380 -> 916 ms Against where this started two changes ago, the home page's first paint has gone from 3124 ms to 692 ms. One thing removed rather than added. The first version of this preloaded each page's banner photograph by hand, and that was a second download of a file the page already had: React emits a preload for an image marked fetchPriority=high as it renders, with the exact URL the <img> uses, and mine used the absolute form of the same address. Found by reading the built HTML rather than by trusting it. --- what the structured data says It said the name, a phone number, a locality and a district. It now says: the postal address, including the postcode, read out of the panel's own footer fields rather than typed a second time for the crawler -- the two disagreeing is exactly how a business loses Google's trust in both; opening hours, from a new field in the panel, in the form schema.org wants, shown in the footer as well when it is filled in; every town served as its own City, not just "Kanyakumari district". Someone searching for a car in Marthandam is searching for a town, and a district named on its own does not say the town is covered. Twelve of them; the social profiles, as sameAs, which is how a page claims an account rather than merely linking to one. Only the ones that are filled in; a stable @id, so thirteen pages describe one business rather than thirteen businesses that share a name; hasMap, pointing at the same place the footer's map does. serviceArea is gone: areaServed now says the same thing town by town, and two properties describing one thing with different precision is a pair to keep in step for no gain. Every inner page also carries a BreadcrumbList, so a result prints "niteshacars.in > Wedding car rental" instead of a bare URL. The leaf is the title cut at the business name or the place, because a breadcrumb is read at a glance and the full title is not. robots.txt stops sending crawlers into the panel -- a login page can only ever rank for the business's own name and disappoint whoever clicks it -- while still allowing the three image readers, because a photograph that cannot be fetched cannot be in image search. Checked: all thirteen routes render clean, the structured data parses on every page, and exactly one preload for the banner rather than two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0128YzbhrfGdegUSc9RrARRf
1 parent e322f09 commit a836c61

46 files changed

Lines changed: 502 additions & 126 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.htaccess‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,37 @@
3939

4040
ErrorDocument 404 /404.html
4141

42+
# ------------------------------------------------------------- compression --
43+
#
44+
# Every page is real HTML now rather than an empty <div id="root">, which is
45+
# what made the site readable before its JavaScript arrives -- and it made the
46+
# home page 74 KB instead of 3 KB. Compressed it is about 12 KB, so this is
47+
# most of that cost handed back, on the one file that has to arrive before
48+
# anything at all can be drawn.
49+
#
50+
# Brotli first where the host has it: it beats gzip by roughly 15% on markup
51+
# and every browser this site sees supports it. gzip is the fallback, and one
52+
# of the two is on every Apache install worth deploying to.
53+
#
54+
# Images and fonts are left alone deliberately. WebP, AVIF, JPEG and WOFF2 are
55+
# already compressed; running them through again spends CPU on every request to
56+
# make the file very slightly larger.
57+
<IfModule mod_brotli.c>
58+
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css
59+
AddOutputFilterByType BROTLI_COMPRESS application/javascript application/json
60+
AddOutputFilterByType BROTLI_COMPRESS image/svg+xml application/xml
61+
</IfModule>
62+
63+
<IfModule mod_deflate.c>
64+
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
65+
AddOutputFilterByType DEFLATE application/javascript application/json
66+
AddOutputFilterByType DEFLATE image/svg+xml application/xml
67+
# Proxies must not hand a compressed copy to a client that cannot read it.
68+
<IfModule mod_headers.c>
69+
Header append Vary Accept-Encoding
70+
</IfModule>
71+
</IfModule>
72+
4273
# ----------------------------------------------------------------- caching --
4374
<IfModule mod_expires.c>
4475
ExpiresActive On

‎404.html‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
<!-- Local search: tells Google this is a car rental business, what it is
3131
called, and how to reach it. -->
32-
<script type="application/ld+json">{"@context":"https://schema.org","@type":"AutoRental","name":"NiteSha Cars & Bikes","description":"Self-drive car rental across Kanyakumari district, Tamil Nadu.","url":"https://niteshacars.in","telephone":"+916374942976","email":"niteshacars045@gmail.com","image":["https://niteshacars.in/hero-car.webp","https://niteshacars.in/photos/home-hero.webp"],"priceRange":"₹₹","currenciesAccepted":"INR","address":{"@type":"PostalAddress","addressLocality":"Nagercoil","addressRegion":"Tamil Nadu","addressCountry":"IN"},"areaServed":{"@type":"AdministrativeArea","name":"Kanyakumari district","containedInPlace":{"@type":"State","name":"Tamil Nadu"}},"serviceArea":{"@type":"AdministrativeArea","name":"Kanyakumari district, Tamil Nadu"}}</script>
32+
<script type="application/ld+json">{"@context":"https://schema.org","@type":"AutoRental","name":"NiteSha Cars & Bikes","description":"Self-drive car rental across Kanyakumari district, Tamil Nadu.","url":"https://niteshacars.in","telephone":"+916374942976","email":"niteshacars045@gmail.com","image":["https://niteshacars.in/hero-car.webp","https://niteshacars.in/photos/home-hero.webp"],"priceRange":"₹₹","currenciesAccepted":"INR","address":{"@type":"PostalAddress","addressLocality":"Nagercoil","addressRegion":"Tamil Nadu","addressCountry":"IN","postalCode":"629001"},"areaServed":[{"@type":"City","name":"Nagercoil","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Kanyakumari","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Marthandam","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Colachel","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Thuckalay","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Kuzhithurai","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Padmanabhapuram","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Suchindram","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Thiruvattar","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Eraniel","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Boothapandi","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}},{"@type":"City","name":"Azhagiapandipuram","containedInPlace":{"@type":"AdministrativeArea","name":"Kanyakumari district"}}],"@id":"https://niteshacars.in#business","hasMap":"https://www.google.com/maps?q=Nagercoil%2C%20Kanyakumari%20district%2C%20Tamil%20Nadu"}</script>
3333

3434
<!-- Poppins is served from this site now (the @font-face rules are in
3535
index.css), so there is no third-party stylesheet to wait on and no
@@ -40,8 +40,8 @@
4040
moment after it. The rest of the weights are discovered normally. -->
4141
<link rel="preload" href="/fonts/poppins-latin-400.woff2" as="font" type="font/woff2" crossorigin />
4242
<link rel="preload" href="/fonts/poppins-latin-700.woff2" as="font" type="font/woff2" crossorigin />
43-
<script type="module" crossorigin src="/assets/index-CM9Y3lMR.js"></script>
44-
<link rel="modulepreload" crossorigin href="/assets/SectionArt-qkFgxD0U.js">
43+
<script type="module" crossorigin src="/assets/index-Bs50ILuu.js"></script>
44+
<link rel="modulepreload" crossorigin href="/assets/SectionArt-MLBNVipt.js">
4545
<link rel="stylesheet" crossorigin href="/assets/index-BUll9_CM.css">
4646
</head>
4747
<body>

‎about/index.html‎

Lines changed: 5 additions & 3 deletions
Large diffs are not rendered by default.

‎admin/content-defaults.json‎

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,36 @@
1919
"services": {
2020
"heading": "What we hire",
2121
"items": [
22-
{ "to": "/cars", "title": "Self drive cars", "body": "Hatchbacks, sedans and SUVs by the day, week or month. You drive." },
23-
{ "to": "/bikes", "title": "Bike rental", "body": "Scooters and motorcycles by the hour, day or week. Helmets included." },
24-
{ "to": "/wedding-cars", "title": "Wedding cars", "body": "Decorated cars reserved for your date, and vehicles for the family." },
25-
{ "to": "/tourist-vehicles", "title": "Tourist vehicles", "body": "Cars and vans with a driver who knows the district and its roads." },
26-
{ "to": "/monthly", "title": "Monthly rental", "body": "Weeks rather than days, at a rate that drops the longer you keep it." },
27-
{ "to": "/nri", "title": "Coming from abroad?", "body": "Book before you land. Airport pickup and long-stay rates for visiting families." }
22+
{
23+
"to": "/cars",
24+
"title": "Self drive cars",
25+
"body": "Hatchbacks, sedans and SUVs by the day, week or month. You drive."
26+
},
27+
{
28+
"to": "/bikes",
29+
"title": "Bike rental",
30+
"body": "Scooters and motorcycles by the hour, day or week. Helmets included."
31+
},
32+
{
33+
"to": "/wedding-cars",
34+
"title": "Wedding cars",
35+
"body": "Decorated cars reserved for your date, and vehicles for the family."
36+
},
37+
{
38+
"to": "/tourist-vehicles",
39+
"title": "Tourist vehicles",
40+
"body": "Cars and vans with a driver who knows the district and its roads."
41+
},
42+
{
43+
"to": "/monthly",
44+
"title": "Monthly rental",
45+
"body": "Weeks rather than days, at a rate that drops the longer you keep it."
46+
},
47+
{
48+
"to": "/nri",
49+
"title": "Coming from abroad?",
50+
"body": "Book before you land. Airport pickup and long-stay rates for visiting families."
51+
}
2852
]
2953
},
3054
"highlights": {
@@ -98,9 +122,21 @@
98122
"howItWorks": {
99123
"heading": "How it works",
100124
"steps": [
101-
{ "n": "01", "title": "Pick your car", "body": "Browse the fleet and choose what suits the trip — a hatchback for the city, an SUV for the highway." },
102-
{ "n": "02", "title": "Send an enquiry", "body": "Tell us your dates. We'll call back to confirm the car is free and agree the rate." },
103-
{ "n": "03", "title": "Collect and drive", "body": "Bring a valid licence and ID. Pay the deposit, take the keys, and the car is yours." }
125+
{
126+
"n": "01",
127+
"title": "Pick your car",
128+
"body": "Browse the fleet and choose what suits the trip — a hatchback for the city, an SUV for the highway."
129+
},
130+
{
131+
"n": "02",
132+
"title": "Send an enquiry",
133+
"body": "Tell us your dates. We'll call back to confirm the car is free and agree the rate."
134+
},
135+
{
136+
"n": "03",
137+
"title": "Collect and drive",
138+
"body": "Bring a valid licence and ID. Pay the deposit, take the keys, and the car is yours."
139+
}
104140
],
105141
"noteLead": "Deposits are refundable.",
106142
"noteBody": "It is returned after the car comes back, less any extra-KM charges or damage. KM limits and extra-KM rates are listed on every car above, so there is nothing to discover later."
@@ -138,6 +174,7 @@
138174
"Kanyakumari district",
139175
"Tamil Nadu 629001"
140176
],
177+
"hours": "",
141178
"mapQuery": "Nagercoil, Kanyakumari district, Tamil Nadu",
142179
"directionsLabel": "Get directions"
143180
},

‎admin/content.php‎

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,22 +104,29 @@ function field_input(string $name, array $spec, mixed $value): void
104104

105105
echo '<label class="c-label" for="' . e($id) . '">' . e($spec['label']) . '</label>';
106106

107+
// A note under the box, for a field where the shape of the answer matters
108+
// and the label cannot carry it -- opening hours being the case that
109+
// prompted it, where "Mo-Su 07:00-21:00" is the only form Google reads.
110+
$hint = (string) ($spec['hint'] ?? '');
111+
107112
if ($spec['type'] === 'textarea') {
108113
echo '<textarea class="c-input" id="' . e($id) . '" name="' . e($name) . '" rows="3">'
109114
. e((string) $value) . '</textarea>';
110-
return;
111-
}
112-
113-
if ($spec['type'] === 'list') {
115+
} elseif ($spec['type'] === 'list') {
114116
$lines = is_array($value) ? implode("\n", $value) : (string) $value;
115117
echo '<textarea class="c-input" id="' . e($id) . '" name="' . e($name) . '" rows="4"'
116118
. ' placeholder="One per line">' . e($lines) . '</textarea>';
117-
echo '<p class="c-hint">One per line.</p>';
118-
return;
119+
if ($hint === '') {
120+
$hint = 'One per line.';
121+
}
122+
} else {
123+
echo '<input class="c-input" type="text" id="' . e($id) . '" name="' . e($name) . '"'
124+
. ' value="' . e((string) $value) . '">';
119125
}
120126

121-
echo '<input class="c-input" type="text" id="' . e($id) . '" name="' . e($name) . '"'
122-
. ' value="' . e((string) $value) . '">';
127+
if ($hint !== '') {
128+
echo '<p class="c-hint">' . e($hint) . '</p>';
129+
}
123130
}
124131

125132
admin_shell_open($me, 'content.php', 'Website content');

‎admin/src/content.php‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,18 @@ function content_schema(): array
153153
],
154154
'footer' => [
155155
'label' => 'Footer',
156-
'note' => 'The band at the bottom of every page. The map is built from what you type in "Where the map should point" -- a place name or an address is enough, the same thing you would type into Google Maps.',
156+
'note' => 'The band at the bottom of every page. The map is built from what you type in "Where the map should point" -- a place name or an address is enough, the same thing you would type into Google Maps. The address and the opening hours are also what Google reads to build the panel it shows beside a local search result, so the more exact they are the better that panel is.',
157157
'fields' => [
158158
'blurb' => ['label' => 'Line under the business name', 'type' => 'textarea'],
159159
'locationHeading' => ['label' => 'Heading above the map', 'type' => 'text'],
160160
'address' => ['label' => 'Address', 'type' => 'list'],
161+
'hours' => [
162+
'label' => 'Opening hours',
163+
'type' => 'text',
164+
'hint' => 'Written the way Google expects: Mo-Su 07:00-21:00. '
165+
. 'Two ranges go in one line separated by a comma, '
166+
. 'like Mo-Sa 08:00-20:00, Su 09:00-18:00. Leave it empty if the hours vary.',
167+
],
161168
'mapQuery' => ['label' => 'Where the map should point', 'type' => 'text'],
162169
'directionsLabel' => ['label' => 'Directions link text', 'type' => 'text'],
163170
],
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)