# Cf B2b Website

> Cloudflare Workers + D1 e-commerce site for Shengtuo Tractor. Deploy, DB ops, SEO, product sync.

- Skill: `clowlove/cf-b2b-website` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add clowlove/cf-b2b-website`
- Raw SKILL.md: https://api.skillmd.com/api/skills/clowlove/cf-b2b-website/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: clowlove (https://skillmd.com/u/clowlove)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/clowlove/cf-b2b-website

---


# cf-b2b-website

## Environment

| Item | Value |
|------|-------|
| Worker | `cf-b2b-website` |
| D1 DB | `b2b_database` (remote) |
| KV | `b2b_kv` (contact form) |
| Deploy token | `cfut_ZENfTsTx1akFdl...` |
| URL | `https://cf-b2b-website.sdtractor.workers.dev` |
| Src | `/home/ubuntu/cf_b2b/` |

## Deploy

### Normal deploy
```bash
cd /home/ubuntu/cf_b2b && CLOUDFLARE_API_TOKEN="cfut_..." npx wrangler deploy
```

### Force redeploy (when cached/stale)
```bash
npx wrangler deploy --force
```

> ⚠️ **Pitfall**: `wrangler deploy` can exit 0 with NO visible output even on syntax errors. Always check output contains `Uploaded` and `Deployed` lines. If output is empty, check for JS syntax errors (e.g., duplicate `const` declarations) before retrying.

## D1 Database

### Batch SQL via file
```bash
npx wrangler d1 execute b2b_database --file=/tmp/sql.sql --remote
```
> ⚠️ **Pitfall**: D1 does NOT support `BEGIN;...COMMIT;` in file mode. Execute statements individually — each `UPDATE/SET` on its own line. No transactions.

### Single query
```bash
npx wrangler d1 execute b2b_database --command="SELECT id, name FROM products LIMIT 5;" --remote
```

## Product Catalog Sync (from shengtuo-tractor.com)

Site is **Error 1016** (partially down) but browser still loads content — product images remain valid at `shengtuo-tractor.com/uploadfile/` URLs.

### Extract product list via browser console
```javascript
[...document.querySelectorAll('ul.products_list li')].map(li => {
  const a = li.querySelector('a[href$=".html"]');
  const img = li.querySelector('img');
  return a ? { name: a.title, href: a.href, img: img ? img.src : null } : null;
}).filter(Boolean)
```

### Match strategy
Extract `ST\d+[A-Z]*` pattern from name to map site products → DB products by ID.

### SQL generation pattern
```sql
UPDATE products SET image_url = 'https://www.shengtuo-tractor.com/uploadfile/FILE.jpg' WHERE id = N;
-- One statement per line, no BEGIN/COMMIT
```

## JS File Editing — CRITICAL

> ⚠️ **NEVER use `sed` on `.js` files** — corrupts template literal backtick nesting (`\`` → raw `` ` ``).
> ⚠️ **NEVER use `echo heredoc` to write `.js` files** — same backtick corruption risk.

**Always use `patch()` or `write_file()` only.**

Common error this causes: inline `<script>` tags inside template literals get mangled, producing "has already been declared" or "unexpected token" build errors.

## SEO Keyword Structure (100+ keywords in layout.js defaultKeywords)

Organized in `src/pages/layout.js`:
- **Brand**: China Shengtuo Tractor, Shengtuo Farm Tractor, Chinese Tractor Manufacturer, etc.
- **Product HP** (25–260HP): Compact Tractor, Mini Farm Tractor, Garden Tractor, Utility Tractor, Heavy Duty Farm Tractor
- **Buyer intent**: Farm Tractor for Sale, China Tractor Exporter, Wholesale Farm Tractor, Buy Farm Tractor
- **Farm equipment**: Rotary Tiller, Disc Harrow, Disc Plough, Front Loader, Backhoe Loader, Hay Baler
- **Long-tail**: 4WD Farm Tractor for Sale, Diesel Farm Tractor China, CE Farm Tractor Manufacturer, OEM Agricultural Tractor
- **Model series**: ST504, ST704, ST804, ST1154, ST1354, ST1604, ST1804, ST2204, ST2404 Tractor
- **Country markets**: Australia Tractor Supplier, South Africa Farm Tractor, Brazil Tractor Manufacturer, Romania Agricultural Tractor

### Homepage Title Override Pattern (IMPORTANT)
`createLayout` uses `isHome = (title === 'Home')` to set special SEO title. When passing site_name (e.g. 'China Shengtuo Farm Tractor Manufacturer') instead of 'Home', `pageTitlePrefix` won't trigger and canonical breaks.

**Always do this for homepage**:
```javascript
const homeTitle = 'China Shengtuo Farm Tractor Manufacturer | 25HP-260HP Agricultural Tractors & Farm Equipment';
const html = createLayout('Home', content, scripts, desc, false);
const finalHtml = html.replace('<title>Shengtuo Tractor</title>', `<title>${homeTitle}</title>`);
return new Response(finalHtml, { ... });
```

### Canonical URL Logic
```javascript
const isHome = title === 'Home';
const canonicalPath = isHome ? '/' : '/' + title.toLowerCase().replace(/\s+/g,'-').replace(/[^a-z0-9-/]/g,'');
```

### Sitemap + Robots.txt
Both generated by `src/pages/sitemap.js`:
- `/sitemap.xml` — 42 product URLs + 4 static pages
- `/robots.txt` — allows all except /api/ and /admin/

### Product Sort Order — FIX BOTH FILES
Products should show Tractors first, then Equipment. Both of these need the same SQL `ORDER BY CASE` pattern:
1. `src/pages/products.js` — server-rendered page
2. `src/api/handlers/products.js` — API endpoint

SQL pattern:
```sql
ORDER BY CASE WHEN category = 'SHENGTUO TRACTOR' THEN 1
              WHEN category = 'SHENGTUO FARM EQUIPMENT' THEN 2
              ELSE 3 END, name
```
> ⚠️ **Pitfall**: Even if products.js is fixed, if the API returns wrong order, pages that rely on API data will show wrong order. Fix BOTH files.

### Product Detail Page Keywords
Build from DB fields dynamically:
```javascript
const hp = product.hp_range || '';
const spec = product.key_spec || '';
metaKeywords = `${product.name}, Shengtuo ${category}, farm equipment, ${hp} tractor, agricultural machinery China, ${spec}`;
```

## Tech Stack
- Cloudflare Workers (Pages-style routing via `router.js`)
- D1 (SQLite) for product DB
- KV for contact form storage
- No build step — pure ES module `.js` files
