From 1f8985e4b23e8435ae82fbeb91923e7f704565fd Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:57:35 +0700 Subject: [PATCH 01/11] feat: add SEO metadata, structured data and camera detail links --- index.html | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index aa872ce..ddc5167 100644 --- a/index.html +++ b/index.html @@ -5,8 +5,45 @@ - - Camera giao thông TP.HCM + + + + + + + + + + + + + + + + Camera giao thông TP.HCM trực tuyến | ePlus.DEV + + + +
+
+ ← Danh sách camera + + + +
+ + +
+

Không tìm thấy camera

+

Camera này không tồn tại hoặc đã được gỡ khỏi danh sách.

+

Quay về danh sách camera giao thông TP.HCM

+
+ +
+
+ <?= h('Camera giao thông ' . $cameraName . ', ' . $district) ?> +
Đang tải
+
+
+
Camera giao thông TP.HCM
+

+

+
+ + Mã camera: + Tự làm mới sau 10 giây +
+
+ Hình ảnh phụ thuộc vào nguồn camera giao thông TP.HCM và có thể tạm thời không khả dụng. + Trang sẽ tự làm mới ảnh khi bạn đang mở tab này. +
+
+
+ + + + + + + +
+ + From 6776de2661eac02753a6b12544f77a950c7f86f7 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:58:57 +0700 Subject: [PATCH 03/11] feat: generate camera sitemap dynamically --- sitemap.php | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 sitemap.php diff --git a/sitemap.php b/sitemap.php new file mode 100644 index 0000000..88ebd64 --- /dev/null +++ b/sitemap.php @@ -0,0 +1,50 @@ +'; + exit; +} + +$lastModified = max( + (int) @filemtime($dataFile), + (int) @filemtime($indexFile), + (int) @filemtime($detailFile) +); +$lastmod = gmdate('Y-m-d', $lastModified ?: time()); + +echo '' . PHP_EOL; +echo '' . PHP_EOL; +echo ' ' . xml(BASE_URL . '/') . '' . $lastmod . '' . PHP_EOL; + +$seen = []; +foreach ($data as $camera) { + $id = (string) ($camera['CamId'] ?? ''); + if (!preg_match('/^[a-f0-9]{24}$/i', $id) || isset($seen[$id])) { + continue; + } + + $seen[$id] = true; + $url = BASE_URL . '/camera.php?id=' . rawurlencode($id); + echo ' ' . xml($url) . '' . $lastmod . '' . PHP_EOL; +} + +echo '' . PHP_EOL; From 63c28eac70b6cb9023b67c3650b441ce036f85df Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:58:59 +0700 Subject: [PATCH 04/11] seo: add robots directives --- robots.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 robots.txt diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..fe4e630 --- /dev/null +++ b/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /vendor/ + +Sitemap: https://camera-hcm.eplus.dev/sitemap.xml From 8caf4376dff35d6f8f4476be3b59c70b574ecc08 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:59:02 +0700 Subject: [PATCH 05/11] seo: add sitemap index --- sitemap.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 sitemap.xml diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..8195ce6 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,6 @@ + + + + https://camera-hcm.eplus.dev/sitemap.php + + From 48644ddd0140e89dec8628986a22240ab54bd1f2 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:59:04 +0700 Subject: [PATCH 06/11] feat: add web app manifest --- manifest.webmanifest | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 manifest.webmanifest diff --git a/manifest.webmanifest b/manifest.webmanifest new file mode 100644 index 0000000..59762d9 --- /dev/null +++ b/manifest.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Camera giao thông TP.HCM", + "short_name": "Camera HCM", + "description": "Tra cứu và theo dõi camera giao thông TP.HCM theo khu vực.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "background_color": "#0f172a", + "theme_color": "#0f172a", + "lang": "vi-VN", + "icons": [ + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" + } + ] +} From 7ac15dd765db14c7190cea99b4d233180eb7258a Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:59:07 +0700 Subject: [PATCH 07/11] feat: add site favicon --- favicon.svg | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 favicon.svg diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..896eeb7 --- /dev/null +++ b/favicon.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + From d3e72993f215aede361340c013ed0f53d7e3dfff Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:59:09 +0700 Subject: [PATCH 08/11] seo: add AI discovery metadata --- llms.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 llms.txt diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..1a8abf1 --- /dev/null +++ b/llms.txt @@ -0,0 +1,26 @@ +# Camera giao thông TP.HCM + +> Public traffic-camera viewer for Ho Chi Minh City, maintained by ePlus.DEV. + +Canonical site: https://camera-hcm.eplus.dev/ +Language: Vietnamese (vi-VN) + +## Primary resources + +- Homepage and searchable camera directory: https://camera-hcm.eplus.dev/ +- Sitemap index: https://camera-hcm.eplus.dev/sitemap.xml +- Dynamic camera sitemap: https://camera-hcm.eplus.dev/sitemap.php +- Machine-readable camera catalog: https://camera-hcm.eplus.dev/data-camera.json +- Agent guidance: https://camera-hcm.eplus.dev/agents.md + +## Camera pages + +Each camera has a canonical server-rendered detail page: + +https://camera-hcm.eplus.dev/camera.php?id={CamId} + +Camera IDs are 24-character hexadecimal identifiers. + +## Data notes + +Camera availability and images depend on the public upstream traffic-camera source. Images may be temporarily unavailable. Do not interpret a missing image as proof that a road or camera no longer exists. From c869ad0cae31988d03cfdcfad1436a3e11a691a5 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:59:11 +0700 Subject: [PATCH 09/11] seo: add agent discovery guidance --- agents.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 agents.md diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..5aa0119 --- /dev/null +++ b/agents.md @@ -0,0 +1,31 @@ +# Agent guidance — Camera giao thông TP.HCM + +Canonical origin: https://camera-hcm.eplus.dev/ + +## Purpose + +This site helps users find and view public traffic cameras in Ho Chi Minh City by camera name and district/area. + +## Preferred discovery flow + +1. Use /sitemap.xml or /sitemap.php to discover canonical camera detail pages. +2. Use /data-camera.json for the camera catalog. +3. Use /camera.php?id={CamId} for a human-readable, server-rendered camera page. +4. Treat /proxy.php?id={CamId} as a live image endpoint, not as a stable text document. + +## Data semantics + +- CamId: stable camera identifier used by this site. +- CamName: human-readable camera/intersection name. +- Disctrict: source field containing district/area information. The misspelling is retained for backward compatibility. +- Snapshot images are time-sensitive and may fail temporarily. + +## Crawling + +- Respect robots.txt. +- Prefer canonical URLs from sitemap and rel=canonical. +- Avoid generating or indexing arbitrary pagination/search URL combinations when a canonical camera detail page is available. + +## Attribution + +Site maintained by ePlus.DEV. Camera imagery depends on the Ho Chi Minh City traffic information source. From 616d7bf1babe2655027a8a38439ea8b468e09ce7 Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Oct 2026 09:59:54 +0700 Subject: [PATCH 10/11] ci: validate SEO pages and generated sitemap --- .github/workflows/ci.yml | 50 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb84a49..39f440b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -37,7 +37,10 @@ jobs: run: composer install --no-interaction --prefer-dist --no-progress - name: Lint PHP - run: php -l proxy.php + run: | + for file in ./*.php; do + php -l "$file" + done - name: Validate camera data run: | @@ -62,8 +65,47 @@ jobs: echo "Validated " . count($data) . " cameras.\n"; ' - - name: Smoke-check UI anchors + - name: Validate sitemap + run: | + php sitemap.php > /tmp/camera-sitemap.xml + php -r ' + $data = json_decode(file_get_contents("data-camera.json"), true, 512, JSON_THROW_ON_ERROR); + $xml = simplexml_load_file("/tmp/camera-sitemap.xml"); + if ($xml === false) { + fwrite(STDERR, "Generated sitemap is invalid XML.\n"); + exit(1); + } + $expected = count($data) + 1; + $actual = count($xml->url); + if ($actual !== $expected) { + fwrite(STDERR, "Expected {$expected} sitemap URLs, got {$actual}.\n"); + exit(1); + } + echo "Validated {$actual} sitemap URLs.\n"; + ' + grep -q 'https://camera-hcm.eplus.dev/sitemap.php' sitemap.xml + grep -q 'Sitemap: https://camera-hcm.eplus.dev/sitemap.xml' robots.txt + + - name: Smoke-check SEO anchors run: | grep -q 'id="cameraGrid"' index.html grep -q 'id="searchInput"' index.html - grep -q 'proxy.php?id=' index.html + grep -q 'rel="canonical" href="https://camera-hcm.eplus.dev/"' index.html + grep -q 'application/ld+json' index.html + grep -q 'camera.php?id=' index.html + grep -q 'max-image-preview:large' index.html + grep -q 'favicon.svg' index.html + grep -q 'manifest.webmanifest' index.html + + - name: HTTP smoke test + run: | + FIRST_ID="$(php -r '$data=json_decode(file_get_contents("data-camera.json"), true, 512, JSON_THROW_ON_ERROR); echo $data[0]["CamId"];')" + php -S 127.0.0.1:8080 > /tmp/camera-php-server.log 2>&1 & + SERVER_PID=$! + trap 'kill "$SERVER_PID"' EXIT + sleep 1 + + curl -fsS "http://127.0.0.1:8080/" | grep -q 'Camera giao thông TP.HCM' + curl -fsS "http://127.0.0.1:8080/camera.php?id=$FIRST_ID" | grep -q 'https://camera-hcm.eplus.dev/camera.php?id=' + curl -fsS "http://127.0.0.1:8080/camera.php?id=$FIRST_ID" | grep -q 'application/ld+json' + curl -fsS "http://127.0.0.1:8080/sitemap.php" | grep -q ' Date: Tue, 6 Oct 2026 09:59:56 +0700 Subject: [PATCH 11/11] docs: document SEO and discovery endpoints --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index cf2e51b..ae4bf31 100644 --- a/README.md +++ b/README.md @@ -57,3 +57,20 @@ Hình ảnh camera được lấy từ Cổng thông tin giao thông TP.HCM. Tì --- © ePlus.DEV + + +## SEO & discovery + +Production URL: + +The project exposes crawlable resources in addition to the JavaScript camera directory: + +- `camera.php?id={CamId}` — server-rendered canonical page for each camera. +- `sitemap.xml` — sitemap index. +- `sitemap.php` — dynamic sitemap containing the homepage and all camera detail pages. +- `robots.txt` — crawler directives and sitemap discovery. +- `llms.txt` — concise AI/LLM discovery file. +- `agents.md` — machine-agent guidance and data semantics. +- `manifest.webmanifest` and `favicon.svg` — app/site identity metadata. + +The homepage and detail pages include canonical URLs, social metadata and Schema.org JSON-LD.