Docker + Readme
This commit is contained in:
@@ -1,15 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
.agents
|
.agents
|
||||||
node_modules
|
npm-debug.log*
|
||||||
dist
|
|
||||||
*.log
|
|
||||||
Dockerfile
|
|
||||||
docker-compose.yml
|
|
||||||
README.md
|
|
||||||
*.md
|
|
||||||
bullet
|
|
||||||
ellipsis
|
|
||||||
em
|
|
||||||
left
|
|
||||||
right
|
|
||||||
|
|||||||
28
Caddyfile
28
Caddyfile
@@ -1,22 +1,12 @@
|
|||||||
# Caddy inside the Docker container — listens on :80
|
|
||||||
# The host Caddy reverse-proxies to this container and handles HTTPS.
|
|
||||||
:80 {
|
:80 {
|
||||||
root * /srv
|
root * /srv
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
|
try_files {path} {path}/ /index.html
|
||||||
|
file_server
|
||||||
|
|
||||||
header {
|
header {
|
||||||
X-Content-Type-Options nosniff
|
X-Content-Type-Options "nosniff"
|
||||||
X-Frame-Options DENY
|
Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
Referrer-Policy strict-origin-when-cross-origin
|
X-Frame-Options "SAMEORIGIN"
|
||||||
Permissions-Policy "geolocation=(), microphone=(), camera=()"
|
}
|
||||||
-Server
|
|
||||||
}
|
|
||||||
|
|
||||||
# Long-term caching for hashed assets
|
|
||||||
@assets path /assets/* /images/* /logo.svg /public/*
|
|
||||||
header @assets Cache-Control "public, max-age=31536000, immutable"
|
|
||||||
|
|
||||||
# SPA fallback: prerendered routes get their own index.html, rest falls back
|
|
||||||
try_files {path} {path}/index.html /index.html
|
|
||||||
file_server
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ FROM node:22-alpine AS build
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -14,5 +14,3 @@ COPY Caddyfile /etc/caddy/Caddyfile
|
|||||||
COPY --from=build /app/dist /srv
|
COPY --from=build /app/dist /srv
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
|
||||||
|
|||||||
284
README.md
284
README.md
@@ -1,293 +1,41 @@
|
|||||||
# Bay Area IT — Website
|
# Bay Area IT
|
||||||
|
|
||||||
Marketing website for **Bay Area IT**, an IT service provider based in Corpus Christi, TX.
|
Vite/React marketing site for `bayareait.services`.
|
||||||
Built with React 19, Vite 6, TypeScript, and Tailwind CSS. Deployed as a static site served by Caddy inside Docker.
|
|
||||||
|
|
||||||
---
|
## Local
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Tech Stack](#tech-stack)
|
|
||||||
- [Project Structure](#project-structure)
|
|
||||||
- [Local Development](#local-development)
|
|
||||||
- [Build & Preview](#build--preview)
|
|
||||||
- [Deployment (Docker + Caddy)](#deployment-docker--caddy)
|
|
||||||
- [Adding Content](#adding-content)
|
|
||||||
- [Scripts Reference](#scripts-reference)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
| Layer | Technology |
|
|
||||||
|---|---|
|
|
||||||
| UI Framework | React 19 |
|
|
||||||
| Build Tool | Vite 6 |
|
|
||||||
| Language | TypeScript 5 |
|
|
||||||
| Styling | Tailwind CSS 3 |
|
|
||||||
| Routing | React Router 7 |
|
|
||||||
| Animations | Framer Motion 12, GSAP 3 |
|
|
||||||
| Smooth Scroll | Lenis |
|
|
||||||
| Web Server | Caddy 2 (inside Docker) |
|
|
||||||
| Containerization | Docker + Docker Compose |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
bayarea/
|
|
||||||
├── components/ # Shared UI components
|
|
||||||
│ ├── Navbar.tsx
|
|
||||||
│ ├── Footer.tsx
|
|
||||||
│ ├── Hero.tsx
|
|
||||||
│ ├── Services.tsx
|
|
||||||
│ ├── Blog.tsx
|
|
||||||
│ ├── CTA.tsx
|
|
||||||
│ ├── Testimonials.tsx
|
|
||||||
│ ├── Process.tsx
|
|
||||||
│ ├── AreasWeServe.tsx
|
|
||||||
│ ├── SEO.tsx # Head/meta tag injection
|
|
||||||
│ ├── Breadcrumb.tsx
|
|
||||||
│ └── LoadingScreen.tsx
|
|
||||||
├── src/
|
|
||||||
│ ├── pages/ # Route-level page components
|
|
||||||
│ │ ├── HomePage.tsx
|
|
||||||
│ │ ├── AboutPage.tsx
|
|
||||||
│ │ ├── ServicesPage.tsx
|
|
||||||
│ │ ├── ServicePage.tsx # Dynamic: /services/:slug
|
|
||||||
│ │ ├── BlogPage.tsx
|
|
||||||
│ │ ├── BlogPostPage.tsx # Dynamic: /blog/:slug
|
|
||||||
│ │ ├── LocationPage.tsx # Dynamic: /locations/:slug
|
|
||||||
│ │ ├── LocationsPage.tsx
|
|
||||||
│ │ ├── ContactPage.tsx
|
|
||||||
│ │ ├── PrivacyPolicyPage.tsx
|
|
||||||
│ │ ├── TermsOfServicePage.tsx
|
|
||||||
│ │ └── LegalPage.tsx
|
|
||||||
│ ├── data/
|
|
||||||
│ │ └── seoData.ts # All location, service, and blog post data
|
|
||||||
│ ├── routes/ # Route definitions
|
|
||||||
│ └── index.css # Global styles
|
|
||||||
├── scripts/
|
|
||||||
│ ├── prerender-routes.ts # Generates per-route index.html files
|
|
||||||
│ ├── prune-dist-assets.mjs # Removes unused assets from dist/
|
|
||||||
│ ├── generate-sitemap.ts # Generates public/sitemap.xml
|
|
||||||
│ ├── generate-robots.ts # Generates public/robots.txt
|
|
||||||
│ └── optimize-images.mjs # Converts images to WebP
|
|
||||||
├── public/
|
|
||||||
│ ├── assets/services/ # Service page images (.webp)
|
|
||||||
│ ├── images/blog/ # Blog post images (.webp)
|
|
||||||
│ ├── sitemap.xml
|
|
||||||
│ └── robots.txt
|
|
||||||
├── Dockerfile # Multi-stage: Node build → Caddy serve
|
|
||||||
├── docker-compose.yml # Runs the container on localhost:8080
|
|
||||||
├── Caddyfile # Caddy config inside the container
|
|
||||||
├── caddy-host.snippet # Paste this into your host Caddy config
|
|
||||||
├── tailwind.config.cjs
|
|
||||||
├── postcss.config.cjs
|
|
||||||
├── vite.config.ts
|
|
||||||
└── tsconfig.json
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Local Development
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Node.js 22+ (or use [nvm](https://github.com/nvm-sh/nvm))
|
|
||||||
- npm
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Start the dev server
|
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
The app runs at **http://localhost:3012**
|
## Production build
|
||||||
|
|
||||||
Hot Module Replacement (HMR) is enabled. If port 3012 is taken, Vite picks the next available port automatically.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Build & Preview
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Production build (Vite + prerender + asset pruning)
|
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Preview the production build locally
|
|
||||||
npm run preview
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The build pipeline runs three steps in sequence:
|
The production build regenerates `robots.txt` and `sitemap.xml`, runs Vite, prunes unused assets, and prerenders route HTML into `dist/`.
|
||||||
|
|
||||||
1. **`vite build`** — bundles and outputs to `dist/`
|
## Docker
|
||||||
2. **`prune-dist-assets.mjs`** — removes any unreferenced files from `dist/assets/`
|
|
||||||
3. **`prerender-routes.ts`** — writes a unique `index.html` per route into `dist/` with correct `<title>`, `<meta>`, canonical URLs, Open Graph tags, and JSON-LD schema
|
|
||||||
|
|
||||||
The result is a fully static site where every URL has its own HTML file — no server-side rendering required.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Deployment (Docker + Caddy)
|
|
||||||
|
|
||||||
This project uses a **two-Caddy setup**:
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet
|
|
||||||
└── Host Caddy (HTTPS, port 443)
|
|
||||||
└── Docker container (internal port 80 → host localhost:8080)
|
|
||||||
└── Caddy inside container
|
|
||||||
└── /srv (built static files)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1 — Build and start the container
|
|
||||||
|
|
||||||
On your server, copy the project files (or clone the repo), then run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d --build
|
docker compose up --build -d
|
||||||
```
|
```
|
||||||
|
|
||||||
This builds the image (Node 22 compiles the app, Caddy serves it) and starts the container.
|
The container serves the built site on `http://127.0.0.1:8080`.
|
||||||
The app is now available at **http://localhost:8080** — only reachable from the server itself.
|
|
||||||
|
|
||||||
To verify:
|
## Reverse proxy with host Caddy
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -I http://localhost:8080
|
|
||||||
# HTTP/1.1 200 OK
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2 — Configure the host Caddy
|
|
||||||
|
|
||||||
Add the following to your host Caddy config (usually `/etc/caddy/Caddyfile`).
|
|
||||||
A ready-to-paste version is in `caddy-host.snippet`:
|
|
||||||
|
|
||||||
```caddy
|
```caddy
|
||||||
bayareait.services, www.bayareait.services {
|
bayareait.services {
|
||||||
encode zstd gzip
|
encode zstd gzip
|
||||||
reverse_proxy localhost:8080
|
reverse_proxy 127.0.0.1:8080
|
||||||
|
|
||||||
@www host www.bayareait.services
|
|
||||||
redir @www https://bayareait.services{uri} permanent
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Replace `bayareait.services` with your actual domain.
|
## Launch notes
|
||||||
|
|
||||||
### Step 3 — Reload host Caddy
|
- Static assets are served by Caddy inside the container.
|
||||||
|
- SPA fallback is enabled while prerendered route folders still resolve directly.
|
||||||
```bash
|
- Claims such as `24/7`, `30+ local businesses`, and legal company details should be verified before launch.
|
||||||
sudo systemctl reload caddy
|
|
||||||
# or
|
|
||||||
caddy reload --config /etc/caddy/Caddyfile
|
|
||||||
```
|
|
||||||
|
|
||||||
Caddy automatically obtains and renews an SSL certificate from Let's Encrypt — no manual certificate management needed.
|
|
||||||
|
|
||||||
### Updating the site
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pull latest changes, rebuild, restart
|
|
||||||
git pull
|
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
Zero-downtime: Docker Compose replaces the old container while the new one starts.
|
|
||||||
|
|
||||||
### Useful Docker commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View logs
|
|
||||||
docker compose logs -f
|
|
||||||
|
|
||||||
# Stop the container
|
|
||||||
docker compose down
|
|
||||||
|
|
||||||
# Rebuild without cache
|
|
||||||
docker compose build --no-cache
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Adding Content
|
|
||||||
|
|
||||||
All page content is driven by **`src/data/seoData.ts`**. No new page files needed for standard locations, services, or blog posts — just add a new entry to the right array.
|
|
||||||
|
|
||||||
### Add a location page
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/data/seoData.ts → locationData array
|
|
||||||
{
|
|
||||||
city: "Sinton",
|
|
||||||
slug: "locations/it-support-sinton",
|
|
||||||
title: "IT Support Sinton, TX | Bay Area IT",
|
|
||||||
description: "...",
|
|
||||||
h1: "IT Support for Businesses in Sinton, TX",
|
|
||||||
keywords: ["IT support Sinton", "..."],
|
|
||||||
content: `<p>...</p>`,
|
|
||||||
faq: [
|
|
||||||
{ question: "...", answer: "..." }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add a service page
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/data/seoData.ts → serviceData array
|
|
||||||
{
|
|
||||||
id: "9",
|
|
||||||
slug: "services/cloud-backup",
|
|
||||||
title: "Cloud Backup Services | Bay Area IT",
|
|
||||||
description: "...",
|
|
||||||
h1: "Cloud Backup for Corpus Christi Businesses",
|
|
||||||
keywords: ["cloud backup", "..."],
|
|
||||||
content: `<p>...</p>`,
|
|
||||||
faq: []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add a blog post
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/data/seoData.ts → blogPostData array
|
|
||||||
{
|
|
||||||
slug: "blog/your-post-slug",
|
|
||||||
title: "Your Post Title | Bay Area IT",
|
|
||||||
description: "...",
|
|
||||||
h1: "Your Post Heading",
|
|
||||||
keywords: ["..."],
|
|
||||||
content: `<p>...</p>`,
|
|
||||||
date: "2026-03-25",
|
|
||||||
image: "/images/blog/your-image.webp"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
After adding content, rebuild:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
# or on the server:
|
|
||||||
docker compose up -d --build
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scripts Reference
|
|
||||||
|
|
||||||
| Script | Command | Description |
|
|
||||||
|---|---|---|
|
|
||||||
| Dev server | `npm run dev` | Starts Vite dev server on port 3012 |
|
|
||||||
| Production build | `npm run build` | Vite build + asset pruning + prerendering |
|
|
||||||
| Preview build | `npm run preview` | Serves `dist/` locally via Vite |
|
|
||||||
| Generate sitemap | `npm run generate:seo` | Writes `public/sitemap.xml` and `public/robots.txt` |
|
|
||||||
| Optimize images | `npm run optimize:images` | Converts images in `public/` to WebP |
|
|
||||||
| Prerender only | `npm run prerender:routes` | Re-runs route prerendering on existing `dist/` |
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
services:
|
services:
|
||||||
web:
|
bayarea-site:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: bay-area-affiliates:latest
|
container_name: bayarea-site
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8080:80"
|
- "127.0.0.1:8080:80"
|
||||||
|
|||||||
Reference in New Issue
Block a user