Complete MoreminiMore Astro site migration
@@ -1,5 +1,11 @@
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.DS_Store
|
||||
.gitignore
|
||||
*.log
|
||||
npm-debug.log*
|
||||
.DS_Store
|
||||
.astro
|
||||
|
||||
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
||||
SES_ACCESS_KEY_ID=
|
||||
SES_SECRET_ACCESS_KEY=
|
||||
SES_REGION=ap-southeast-1
|
||||
META_ACCESS_TOKEN=
|
||||
GA4_API_SECRET=
|
||||
SES_FROM_EMAIL=kunthawat@moreminimore.com
|
||||
CONTACT_TO_EMAIL=kunthawat@moreminimore.com
|
||||
META_PIXEL_ID=418349260078648
|
||||
GA4_MEASUREMENT_ID=G-74BHREDLC3
|
||||
TRUST_PROXY=0
|
||||
PORT=4321
|
||||
17
Dockerfile
@@ -1,4 +1,3 @@
|
||||
# Build the static Astro site
|
||||
FROM node:22-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
@@ -6,9 +5,13 @@ RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Serve static files with a small, unprivileged web server
|
||||
FROM nginxinc/nginx-unprivileged:1.27-alpine AS production
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 8080
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
FROM node:22-alpine AS production
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY server.js ./server.js
|
||||
USER node
|
||||
EXPOSE 4321
|
||||
CMD ["node", "server.js"]
|
||||
|
||||
11
README.md
@@ -1,6 +1,6 @@
|
||||
# MoreminiMore redesign
|
||||
|
||||
A static Astro redesign for MoreminiMore, retaining verified service, case study, portfolio, and contact information from moreminimore.com.
|
||||
An Astro website for MoreminiMore with four service landing pages and a same-origin Express contact API delivered through Amazon SES.
|
||||
|
||||
## Local development
|
||||
|
||||
@@ -13,13 +13,18 @@ npm run dev
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Docker deployment
|
||||
|
||||
```sh
|
||||
docker build -t moreminimore-site .
|
||||
docker run --rm -p 8080:8080 moreminimore-site
|
||||
docker run --rm --env-file .env -p 4321:4321 moreminimore-site
|
||||
```
|
||||
|
||||
The site is served at `http://localhost:8080`.
|
||||
The site is served at `http://localhost:4321`. Copy `.env.example` to `.env`. Production email delivery requires `SES_ACCESS_KEY_ID`, `SES_SECRET_ACCESS_KEY`, and `SES_REGION`; the sender and recipient both default to `kunthawat@moreminimore.com` and can be overridden with `SES_FROM_EMAIL` and `CONTACT_TO_EMAIL`.
|
||||
|
||||
`META_ACCESS_TOKEN` and `GA4_API_SECRET` are optional. When configured, a successful email submission also sends consent-scoped server conversions. `META_PIXEL_ID` defaults to `418349260078648` and `GA4_MEASUREMENT_ID` defaults to `G-74BHREDLC3`. Provider timeouts or failures never change the successful form response. In non-production mode, valid submissions are accepted without sending email or provider conversions so the flow can be tested safely.
|
||||
|
||||
`TRUST_PROXY` defaults to `0`, so forwarded client-IP headers are ignored. Behind a known reverse proxy, set it to the explicit hop count (`1`–`3`) that matches the deployment path; never set it to `true`. Contact requests are rate-limited per resolved client IP.
|
||||
|
||||
36
TRACKING.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# MoreminiMore tracking
|
||||
|
||||
Tracking is enabled in production builds and omitted from the Astro development server so local previews do not pollute production data. Production starts with every non-essential Google Consent Mode signal denied. No analytics, advertising, or recorder script is requested until ConsentOS emits the verified `consentos:consent-change` event with the corresponding accepted category.
|
||||
|
||||
## Installed tools
|
||||
|
||||
- ConsentOS — the loader follows a non-networking denied-default bootstrap and runs before any tracking tag. Its `analytics` category gates GA4, Umami, and the recorder; `marketing` gates Meta Pixel.
|
||||
- Google Analytics 4 — measurement ID `G-74BHREDLC3`.
|
||||
- Meta Pixel — pixel ID `418349260078648`, including PageView event IDs.
|
||||
- Umami Analytics — website ID `b2e87a6c-0b64-43c8-bb09-e406ffca0af1`.
|
||||
- Umami Recorder — 15% sample rate, strict masking, five-minute maximum sessions. `.contact-form` is excluded through both the recorder block selector and rrweb's `rr-block` class.
|
||||
|
||||
If a visitor withdraws analytics or marketing consent after a tag was loaded, the page reloads after ConsentOS stores the new choice. The reloaded page stays tag-free for the withdrawn category. The Meta noscript fallback is intentionally omitted because it cannot be safely consent-gated without JavaScript.
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Trigger | Properties |
|
||||
| --- | --- | --- |
|
||||
| `page_view` / `PageView` | Each production page load | Automatic page data |
|
||||
| `contact_clicked` / `Contact` | Phone, email, or LINE link | `method`, `location` |
|
||||
| `service_selected` / `ViewContent` | Service landing-page link | `service_path`, `location` |
|
||||
| `form_started` | First focus inside a contact form | `form_location` |
|
||||
| `form_submitted` / `Lead` | Contact API confirms a successful submission | `form_location`, shared random event ID for Meta deduplication |
|
||||
|
||||
No names, phone numbers, email addresses, form values, or other personally identifiable information are included in these client-side events.
|
||||
|
||||
## Contact-form tracking
|
||||
|
||||
Each page uses the same short contact form. `form_started` is emitted once on the first focus. The client creates a UUID event ID for each validated submission and sends a boolean snapshot of the already-applied ConsentOS state. The same ID is retained across network or ambiguous-response retries; it rotates only after confirmed success or an actual edit to the form contents. `form_submitted` and the Meta standard `Lead` browser event are emitted only after `/api/contact` returns success. The browser event contains only the page or service location and reuses the same event ID as Meta CAPI for deduplication.
|
||||
|
||||
The contact endpoint sends email through Amazon SES first. Only after SES succeeds:
|
||||
|
||||
- With marketing consent and `META_ACCESS_TOKEN`, Meta CAPI receives `Lead`, the shared event ID, server IP/user agent, validated `_fbp`/`_fbc`, and normalized name/contact values hashed with SHA-256. Thai phone numbers are normalized to country code `66` before hashing. Raw form values are not sent to Meta.
|
||||
- With analytics consent, `GA4_API_SECRET`, and a safely retrieved GA client ID, Google Measurement Protocol receives `form_submitted`, session/context identifiers, explicit advertising consent state, and no PII, hashes, or user properties. The browser helper skips its GA copy to avoid a duplicate event; Umami still receives the PII-free event.
|
||||
|
||||
Provider requests have short timeouts and are handled with `Promise.allSettled`, so missing tokens, upstream errors, or timeouts never change a successful form response. There is no public conversion relay route. Event IDs are UUID-v4 validated and retained in a bounded 15-minute pending/completed registry. Concurrent pending requests receive a processing response; a retry of a completed delivery receives success without sending a second email, while failed SES attempts release the ID for retry. Delivery and provider logs contain status only and never include form values, credentials, tokens, or upstream response bodies.
|
||||
@@ -5,5 +5,5 @@ export default defineConfig({
|
||||
site: 'https://moreminimore.com',
|
||||
output: 'static',
|
||||
integrations: [sitemap()],
|
||||
build: { format: 'file' },
|
||||
build: { format: 'directory' },
|
||||
});
|
||||
|
||||
5669
package-lock.json
generated
22
package.json
@@ -3,7 +3,23 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {"dev": "astro dev", "build": "astro check && astro build", "preview": "astro preview"},
|
||||
"dependencies": {"@astrojs/sitemap": "^3.2.1", "astro": "^5.10.0"},
|
||||
"devDependencies": {"@astrojs/check": "^0.9.4", "typescript": "^5.8.3"}
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sesv2": "^3.883.0",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"astro": "^7.1.1",
|
||||
"express": "^5.1.0",
|
||||
"express-rate-limit": "^8.2.1",
|
||||
"gsap": "^3.15.0",
|
||||
"nodemailer": "^9.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 965 KiB |
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 992 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 911 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 745 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 996 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 693 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 670 KiB |
|
After Width: | Height: | Size: 768 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 815 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1016 KiB |
|
After Width: | Height: | Size: 887 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 899 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 731 KiB |
|
After Width: | Height: | Size: 1011 KiB |
|
After Width: | Height: | Size: 980 KiB |
|
After Width: | Height: | Size: 600 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 796 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 866 KiB |
|
After Width: | Height: | Size: 918 KiB |
|
After Width: | Height: | Size: 289 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 956 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.5 MiB |
BIN
public/images/illustrations/ai-consult.jpg
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
public/images/illustrations/automation-workflow.jpg
Normal file
|
After Width: | Height: | Size: 292 KiB |
BIN
public/images/illustrations/brand-direction.jpg
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
public/images/illustrations/marketing-consult.jpg
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
public/images/illustrations/website-development.jpg
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
public/images/logos/footer-logo-long-black.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/logos/logo-long-black.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/images/portfolio/baofuling.png
Normal file
|
After Width: | Height: | Size: 706 KiB |
BIN
public/images/portfolio/dataroot.png
Normal file
|
After Width: | Height: | Size: 406 KiB |
BIN
public/images/portfolio/dealplustech.png
Normal file
|
After Width: | Height: | Size: 768 KiB |
BIN
public/images/portfolio/jetindustries.png
Normal file
|
After Width: | Height: | Size: 911 KiB |
BIN
public/images/portfolio/lawyernoom.png
Normal file
|
After Width: | Height: | Size: 475 KiB |
BIN
public/images/portfolio/leudjorakhe.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
public/images/portfolio/trainersunny.png
Normal file
|
After Width: | Height: | Size: 849 KiB |
BIN
public/images/portfolio/tuanthong.png
Normal file
|
After Width: | Height: | Size: 897 KiB |
BIN
public/images/portfolio/underdog.png
Normal file
|
After Width: | Height: | Size: 724 KiB |
21
public/llms.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
# MoreminiMore
|
||||
|
||||
MoreminiMore ให้คำปรึกษาและบริการด้านเว็บไซต์ การตลาดออนไลน์ ระบบอัตโนมัติ และ AI สำหรับ SME โดยเริ่มจากโจทย์ธุรกิจและข้อมูลจริง
|
||||
|
||||
## ข้อมูลหลัก
|
||||
- หน้าแรก: https://moreminimore.com/
|
||||
- เกี่ยวกับเรา: https://moreminimore.com/about/
|
||||
- บริการทั้งหมด: https://moreminimore.com/services/
|
||||
- Website Development: https://moreminimore.com/services/website-development/
|
||||
- Marketing Consult: https://moreminimore.com/services/marketing-consult/
|
||||
- Automation Workflow: https://moreminimore.com/services/automation-workflow/
|
||||
- AI Consult: https://moreminimore.com/services/ai-consult/
|
||||
- คำถามที่พบบ่อย: https://moreminimore.com/faq/
|
||||
- บทความ: https://moreminimore.com/blog/
|
||||
- นโยบายความเป็นส่วนตัว: https://moreminimore.com/privacy/
|
||||
- เงื่อนไขการใช้งาน: https://moreminimore.com/terms/
|
||||
|
||||
## ติดต่อ
|
||||
- อีเมล: contact@moreminimore.com
|
||||
- โทรศัพท์: +66 80 995 5945
|
||||
- LINE: https://line.me/R/ti/p/@moreminimore
|
||||