Task Tracker
Click a task to mark it complete. Progress saves in your browser automatically.
Bear Grips Dev Hub
Task board for shops.beargrips.com. Last updated: March 30, 2026.
Remaining Tasks
Security Hardening
Most security is done (Stripe verification, Helmet, rate limiting). Remaining: enable CSP, review auth cookies, remove hardcoded title from source index.html.
LLM Integration API
Staff-level API for Claude to create products, manage vendor shops, write SEO/blog content, and handle DFY VIP onboarding at scale. Additive endpoints only.
What's Left
| # | Task | Status | Priority |
|---|---|---|---|
| 1 | Security Hardening — Stripe webhook verification, Helmet, and rate limiting are all done. Remaining: • Enable Content-Security-Policy in helmet config (currently contentSecurityPolicy: false)• Review auth cookie settings (httpOnly, secure, sameSite flags) • Add CSRF protection if session cookies are used • Remove hardcoded <title>Bear Grips Pro Shop</title> from source index.html (line 35) — fixed in dist but will revert on next build | PARTIAL | MEDIUM |
| 2 | LLM Integration API — Staff-level API endpoints for Claude backend access. See LLM API section for full spec. | NOT STARTED | NEW |
SSR / Pre-Rendering for shops.beargrips.com
Priority: CRITICAL — If Google can't render the JavaScript, none of our SEO work matters.
Solution: Self-Hosted Prerender (FREE — use this)
Step 1: Install the open-source prerender server
This is the same rendering engine behind Prerender.io, just self-hosted. github.com/prerender/prerender (6.5K stars, actively maintained)
Step 2: Keep it running with PM2
Step 3: Add Express middleware to detect bots
In the shops Express app, add middleware that checks the User-Agent. If it's a crawler (Googlebot, Bingbot, etc.), proxy the request to the prerender server. Human visitors get the normal React SPA.
That's it. The prerender-node middleware automatically detects bot user-agents and routes them to the local prerender server. Human visitors are unaffected.
Step 4: Add Nginx caching (optional but recommended)
Cache pre-rendered pages so the prerender server doesn't re-render on every bot visit:
Alternative options (if self-hosted prerender doesn't fit)
Option B: Server-Side Rendering (SSR) — best long-term
- Migrate React app to Next.js (SSR/SSG built in)
- Renders on the server before sending to browser — Google sees full HTML natively
- More work upfront but eliminates the prerender layer entirely
- Cost: $0 (Vercel free tier or self-host on existing DO)
Option C: Static Site Generation (SSG) — for pages that don't change often
- Pre-build HTML for public pages at deploy time (homepage, pricing, features, catalog)
- Best performance — this is what the directory sites use
- Only works for pages that don't change often — vendor shops need prerender or SSR
How to verify it works
This should return full HTML with real content — NOT an empty <div id="app"></div>. If you see an empty div, Google sees the same thing = no indexing.
Sitemap & Robots.txt
The Gap: 17 URLs vs. ~6,900+ Total Pages
Pages that exist RIGHT NOW (not in sitemap)
| Page Type | URL Pattern | Count | In Sitemap? |
|---|---|---|---|
| Static/marketing pages | /, /catalog, /help, /resources, etc. | 17 | YES |
| Missing static pages | /pricing, /how-it-works, /features | ~3-5 | NO |
| Vendor shop pages (35-40 vendors) | /proshops/{slug} | ~40 | NO |
| Product pages (all vendors combined) | /proshops/{slug}/product/{id} | ~200-700 | NO |
| Total live pages today | ~260-760 | 2% covered | |
Pages built and ready to deploy (not on site yet)
| Content | Page Count | Status |
|---|---|---|
| Product catalog (category + brand + product pages) | 85 | Built, needs deploy |
| Free design tools + hub page | 13 | Built, needs deploy |
| Niche landing pages (gym, church, sports, etc.) | 117 | Built, needs deploy |
| pSEO pages (resources, comparisons, glossary, guides) | 2,163 | Built, on Cloudflare Pages |
| Blog niche content (tool×niche, service×niche) | 3,808 | Built, needs deploy |
| Deploy backlog total | ~6,186 |
Part 1: Static Sitemap (What You Have Now)
The current sitemap.xml has 17 static URLs. Keep these, but rename the file to sitemap-static.xml and add it to a sitemap index (see below).
Part 2: Dynamic Sitemap for Vendor Shops + Products (CRITICAL)
Every live vendor shop and every published product is a page that should be in the sitemap. This must be generated dynamically from the database.
Dynamic Sitemap Architecture
Create an API endpoint that generates the sitemap XML on the fly from the database:
Rules:
- Only include LIVE/published shops and products — no drafts, no disabled shops
- Use real
lastmoddates from the database (when the shop/product was last updated) - Cache the output for 1 hour — don't query the DB on every request
- If total URLs exceed 50,000, split into multiple sitemap files with a sitemap index
Sitemap Index (ties everything together)
Create a sitemap index at /sitemap.xml that references all sub-sitemaps:
Part 3: Static Sitemap Pages
Rename the current sitemap.xml to sitemap-static.xml and keep it for the static pages:
Notes:
- Update URLs to match actual page paths on the site
- Do NOT include dashboard/vendor pages (anything behind login)
- Vendor shop pages (
/proshops/[shopname]) and product pages go in the dynamicsitemap-shops.xml— see Part 2 above
Robots.txt
Place at: https://shops.beargrips.com/robots.txt
Create the file with this content:
Meta Tags & Canonical URLs
Canonical URLs — URGENT FIX
Add to the <head> of every public page:
React Implementation (copy-paste into your app)
If using React Router, add this hook or component that sets the canonical tag dynamically on every route change. Drop it into your root App component or layout:
If using SSR/prerender: Make sure the canonical tag is in the server-rendered HTML too, not just added by JS. Google prefers to see it in the initial HTML response.
This strips all query params (?ref=, ?sub_id=, etc.) so Google treats every URL variant as the same canonical page. This is the #1 quickest SEO win right now.
Tag Structure (Same for Every Page)
Every public page needs these tags in <head>. The values change per page, but the structure is always the same:
Part 1: Static Pages — Copy Provided (Just Paste These In)
| Page | Title | Description |
|---|---|---|
/ (Homepage) |
Custom Branded Apparel for Your Business — No Inventory, Free Shipping | Launch your own branded merch shop in minutes. Premium Nike, Bella+Canvas, Champion apparel printed on demand. No inventory, no minimums, free USA shipping. Plans from $0/mo. |
/catalog |
Product Catalog — 100+ Premium Apparel Products | Browse our full catalog of custom apparel: tees, hoodies, joggers, tanks, hats, leggings and more. Nike, Bella+Canvas, Champion, Sport-Tek brands. All-inclusive pricing includes printing and free shipping. |
/help |
Help Center — Getting Started, FAQs & Support | Get help with your Bear Grips Pro Shop. Setup guides, design tips, pricing info, shipping details, and answers to common questions. Contact us at proshops@beargrips.com. |
/resources |
Resources — Design Services, Tools & Guides | Free design tools, professional logo design, file formatting services, and step-by-step guides to build and grow your branded apparel shop. |
/resources/design-services |
Professional Design Services — Logos, Mockups & More | Professional design services for your custom apparel shop. Logo design starting at $99, file formatting for $29, mockup creation, and full shop setup. |
/resources/pro-shop-onboarding |
Pro Shop Setup Guide — Launch Your Shop Step by Step | Step-by-step onboarding guide to set up your branded apparel shop. Upload your logo, choose products, set pricing, and start selling — no inventory needed. |
/resources/5-day-launch-kit |
5-Day Launch Kit — Go From Signup to Selling | Launch your custom apparel shop in 5 days. Day-by-day action plan covering design, product selection, pricing, and your first sale. |
/resources/design-services/file-formatting |
File Formatting Service — $29 Print-Ready Conversion | Get your logo converted to print-ready format for $29. We optimize resolution, transparency, and color profiles so your designs look perfect on every product. |
/resources/design-services/add-designs-to-products |
Add Designs to Products — We Apply Your Logo for You | Send us your design and we'll apply it to your selected products with professional mockups. Front and back placement, color variant selection included. |
/resources/design-services/pro-shop-layout |
Pro Shop Layout Service — Professional Shop Design | We design your entire shop layout: header, logo placement, product categories, sections — a polished storefront ready for customers. |
/resources/design-services/graphic-designer-logo |
Custom Logo Design — $99 Professional Logo for Your Brand | Professional custom logo design for $99. Our designers create a unique logo optimized for apparel printing. Includes revisions and print-ready files. |
/resources/design-services/add-additional-logo-design |
Additional Logo Design — Add Another Design to Your Shop | Add a new logo or design variation to your shop. Perfect for seasonal collections, special events, or expanding your product line with fresh designs. |
/resources/design-services/monthly-shop-request |
Monthly Shop Request — Done-For-You VIP Service | Send us one design per month and we handle everything: product selection, mockups, pricing, shop layout. Your shop stays fresh with zero effort on your end. |
/terms-of-service |
Terms of Service | Terms of service for Bear Grips Pro Shops. Read our terms governing use of the custom apparel platform, vendor accounts, and print-on-demand services. |
/privacy-policy |
Privacy Policy | Privacy policy for Bear Grips Pro Shops. How we collect, use, and protect your personal information on our custom apparel platform. |
/refund-policy |
Refund Policy | Refund policy for Bear Grips Pro Shops. Information about returns, exchanges, and refunds for custom printed apparel orders. |
/cancellation-policy |
Cancellation Policy | Cancellation policy for Bear Grips Pro Shops subscription plans. How to cancel, pause, or change your plan at any time. |
OG Image for static pages: Use the Bear Grips logo or a branded social preview image. Same image for all static pages is fine. Recommended size: 1200x630px.
Part 2: Dynamic Meta Tags — Vendor Shops & Product Pages
Vendor Shop Pages: /proshops/{slug}
If vendor has a custom description: use it as the meta description instead of the template above.
Product Pages: /proshops/{slug}/product/{id}
Catalog Product Pages (if separate from vendor pages)
Draft/Unpublished Pages
Any vendor shop or product page that is NOT live/published must have:
This prevents Google from indexing half-built or inactive shops.
Framework Implementation
Use react-helmet or react-helmet-async in the React app. Set the meta tags in each page component using the data already available (vendor name, product name, description, price, image URL).
Schema Markup
This JSON-LD is already on the shops.beargrips.com homepage. Reference copy below for future pages:
Notes:
- Update the logo URL if it's different
- This goes on the homepage only for now
- KJ will provide additional schema for specific pages later
Page Speed & SSL
Page Speed — Target 90+
Test at: pagespeed.web.dev
Common Fixes
- Compress images: Use WebP format. Tools: Squoosh or Sharp
- Lazy load images: Add
loading="lazy"to below-fold images - Minimize JS bundle: Code-split, tree-shake, remove unused dependencies
- Minimize CSS: Remove unused CSS, use PurgeCSS if applicable
- Enable gzip/brotli compression on the server (nginx)
- Cache static assets: Set
Cache-Controlheaders (e.g.,max-age=31536000for images/CSS/JS) - Preload critical resources: Fonts, above-the-fold images
- Defer non-critical JS: Add
deferorasyncto non-essential scripts
SSL / HTTPS
- All pages must load over HTTPS (no mixed content warnings)
- HTTP should 301 redirect to HTTPS automatically
- Test:
http://shops.beargrips.comshould redirect tohttps://shops.beargrips.com
Mobile Responsiveness
- Test all public pages on mobile (Chrome DevTools → Toggle Device Toolbar)
- Key breakpoints: 375px (iPhone SE), 390px (iPhone 14), 768px (iPad)
- No horizontal scrolling, text readable, buttons tappable (44px+ touch targets)
Google Search Console — Verification Tag
The meta tag is already on the shops.beargrips.com homepage. Reference copy below:
Vendor Shop SEO
Priority: HIGH — Every vendor shop and product page is a free SEO page for Bear Grips. Right now Google can see the meta tags but the page body is empty.
What's Working (Good job!)
- Pre-rendered meta tags for bots — title, description, OG tags, Twitter cards
- Vendor name in title and description
- Product mockup image in OG tags (shows when sharing links)
- Product price in OG tags (
product:price:amount) - Vendor logo as favicon per shop
Issue 0: Hide Printify Image URLs (Security)
Current (exposed):
What it should look like:
Fix: Nginx reverse proxy (quickest approach)
Add this to the nginx config for shops.beargrips.com:
Then update the app code to rewrite all Printify mockup URLs:
- Replace
https://images-api.printify.com/mockup/withhttps://shops.beargrips.com/product-images/ - This applies to: og:image, twitter:image, any <img> tags, and the Product schema image field
- The nginx proxy fetches from Printify behind the scenes — users and Google only see our domain
- 7-day cache means the same image isn't re-fetched constantly
Also check for leaks in:
- Product page JavaScript (network tab may show Printify API calls — consider proxying those too)
- Any references to "Printify" in page content, alt tags, or file names
Issue 1: Empty Body Content
Current product page body (what Google sees):
What it should look like (use the same data already in the meta tags):
Same approach for shop pages:
Key points:
- All this data is already available server-side (it's in the meta tags) — just also render it in the body
- The
<img>tag with the product mockup lets Google Images index the product photo - The product list on shop pages creates internal links Google can follow to discover all products
- Normal users will still get the JS redirect immediately — they won't see this HTML
Issue 2: Product Schema Markup
Add JSON-LD Product schema to every product page. This enables rich results in Google (price, availability, image, reviews right in search results).
Add this to the <head> of product pages (alongside existing meta tags):
Dynamic fields to populate from existing data:
| Schema Field | Source |
|---|---|
name | Product title (same as og:title minus store name) |
description | Product description (same as meta description) |
image | Product mockup URL (same as og:image) |
brand.name | Vendor store name |
price | Retail price (same as product:price:amount) |
url | Full product page URL |
availability | Always "InStock" (print on demand = always available) |
Issue 3: Dynamic Sitemap
Google needs a sitemap that includes all live vendor shops and published products. Without it, Google has to discover pages by crawling links — which is slow and incomplete.
Create a dynamic sitemap at: https://shops.beargrips.com/sitemap-shops.xml
This should auto-generate from the database, including:
Implementation notes:
- Generate dynamically from the database — query all vendors with active shops + all published products
- Only include shops/products that are live and published (not drafts)
- Update the main
robots.txtto reference this sitemap too:
Sitemap: https://shops.beargrips.com/sitemap-shops.xml - Submit to Google Search Console after creating it
- As vendors add/remove products, the sitemap auto-updates on next request
- Consider caching the sitemap for 1 hour to avoid regenerating on every request
Issue 4: Richer Default Descriptions
Most vendors keep the default shop description: "Shop at [Name]. Browse our store." — this is thin content that doesn't help SEO. Improve the auto-generated defaults so vendors get better rankings without doing extra work.
Default Shop Description (auto-generated)
Replace the current default with a richer template that pulls from the vendor's store name. Vendor can still override this if they want.
Current default:
Better default:
Default Shop Title
Current:
Better:
Default Product Page Title
Current format is good: [Product Name] - [Store Name]
Consider appending a tail keyword:
Default Product Description Enrichment
When a product uses the default system description, append a vendor-specific line at the end to make each page more unique to Google:
Why this helps vendors:
- "[Store Name] custom apparel" becomes a rankable keyword automatically
- "[Store Name] tee/hoodie/tank" product pages rank for long-tail searches
- "Free shipping" and "Printed in the USA" are trust signals Google likes to surface
- Vendors get SEO traction without writing anything themselves
- Every new product published = new indexed page with unique content
Price Display
Minor fix: Round prices to 2 decimal places in all meta tags. Currently showing raw values like 43.5552 and 26.9152.
Issue 5: Missing Favicon on Product Pages
Shop pages have the vendor's favicon but product pages are missing it. The <link rel="icon"> tag should be on all pages — shop and product.
Issue 6: Image Alt Tags
Product mockup images have no alt attribute. Google Images is a major traffic source — without alt text, our product images won't rank.
Current:
Fix — add descriptive alt text:
Pattern: alt="[Product Name] - [Store Name]"
Apply to:
- All product mockup
<img>tags in the page body - The pre-rendered
<img>in the body content (from Issue 1) - OG image tags should also get proper filenames where possible
Issue 7: Canonical URLs on Product Pages
Every public page (shop page and product page) needs a canonical tag to prevent duplicate content issues.
Add to the <head> of every shop and product page:
Build the URL dynamically from the current page path. This tells Google "this is the one true URL for this page" — prevents indexing of URL variations with query params, trailing slashes, etc.
Issue 8: Noindex Draft/Unpublished Pages
If any product or shop pages are accessible but not yet live (drafts, unpublished, deactivated), they should not be indexed by Google.
Add this to the <head> of any non-live page:
Only live, published shops and products should have index, follow. If a vendor deactivates their shop or unpublishes a product, flip to noindex.
Issue 9: OG Image Dimensions
Add width and height to OG image tags so social platforms (Facebook, LinkedIn, Twitter, iMessage) render previews instantly without fetching the image first.
Add alongside existing og:image tag:
If the actual mockup dimensions differ, use those values instead. Square (1200x1200) or landscape (1200x630) are both fine.
Issue 10: Custom 404 Page
To implement:
- Place
404.htmlin the public/root directory - Configure your app or nginx to serve this file for all 404 responses
- If using Express:
app.use((req, res) => res.status(404).sendFile('404.html')) - If using nginx fallback:
error_page 404 /404.html;
Impact
Once these fixes are in place, every vendor shop becomes a free SEO asset:
- "Gallery Barre apparel" → their shop page ranks
- "Gallery Barre tee" → their product page ranks
- Google Images → product mockups appear in image search
- Rich results → price and availability show in Google search results
- More vendors = more pages = more domain authority for shops.beargrips.com
Favicon & Web Manifest
Favicon files are ready — just add them to shops.beargrips.com. Google shows favicons in search results now, so this matters.
Files to Add
KJ will provide these files (already generated). Place them in the root/public directory of the shops app:
| File | Size | Purpose |
|---|---|---|
favicon.ico | 16+32+48px | Browser tab (legacy browsers) |
favicon-16x16.png | 16x16 | Browser tab |
favicon-32x32.png | 32x32 | Browser tab (retina) |
apple-touch-icon.png | 180x180 | iPhone home screen bookmark |
android-chrome-192x192.png | 192x192 | Android home screen |
android-chrome-512x512.png | 512x512 | Android splash screen |
HTML Tags
Add these to the <head> of every page:
Web Manifest File
Create site.webmanifest in the public/root directory:
Free Tools Pages
Deploy the 7 free design tools + hub page to shops.beargrips.com/free-design-tools/. These are static HTML files — no build step needed.
Files to Deploy
| # | File | URL on Live Site |
|---|---|---|
| 1 | index.html | /free-design-tools/ |
| 2 | background-remover.html | /free-design-tools/background-remover.html |
| 3 | image-cropper.html | /free-design-tools/image-cropper.html |
| 4 | file-compressor.html | /free-design-tools/file-compressor.html |
| 5 | transparent-png-checker.html | /free-design-tools/transparent-png-checker.html |
| 6 | image-to-png-converter.html | /free-design-tools/image-to-png-converter.html |
| 7 | image-resizer.html | /free-design-tools/image-resizer.html |
| 8 | text-design-generator.html | /free-design-tools/text-design-generator.html |
| 9 | assets/ folder | Logo, favicons (must be in /free-design-tools/assets/) |
Steps
1 Create the /free-design-tools/ directory
On the server, create the directory where the tool pages will live:
Adjust the path to match wherever the shops app serves static files from.
2 Upload all files
Upload the hub page + 7 tool pages + assets folder. Use SCP, rsync, or manual upload:
3 Verify all pages load
Open each URL in a browser and confirm they load correctly:
https://shops.beargrips.com/free-design-tools/— hub page with links to all 7 tools- Click each tool link from the hub — all 7 should load
- Bear Grips logo + favicon should appear on every page
- Check mobile (Chrome DevTools → device toolbar)
- No 404 errors, no broken assets
4 Test each tool works
For each of the 7 tools:
- Upload a test image (use any photo or logo)
- Use the tool (resize, crop, remove background, compress, check transparency, convert, generate text)
- Download the result — confirm the output file is correct
- Test on both desktop and mobile
5 Add sitemap entries
Add all 8 free tool URLs to the shops sitemap (sitemap.xml):
6 Add internal link from homepage
Add a link to /free-design-tools/ from the shops.beargrips.com homepage. Options:
- Nav bar: Add "Free Tools" link to the top navigation
- Footer: Add "Free Design Tools" to the footer link section
- Both: Nav + footer for maximum visibility and SEO juice
Link text should be: "Free Design Tools"
Niche Landing Pages (117 Pages)
Deploy 117 niche-specific landing pages to shops.beargrips.com/free-design-tools/[niche-slug]. Same 7 tools, niche-specific copy/SEO targeting long-tail keywords like "custom gym apparel" and "church t-shirt design tools."
Directory Structure
All niche pages live inside a niche/ subfolder within the free-design-tools directory:
All 117 Niche Pages to Upload
| # | File | Live URL |
|---|---|---|
| 1 | after-school-program-apparel.html | /free-design-tools/niche/after-school-program-apparel |
| 2 | auto-shop-apparel.html | /free-design-tools/niche/auto-shop-apparel |
| 3 | bachelorette-party-shirts.html | /free-design-tools/niche/bachelorette-party-shirts |
| 4 | bachelor-party-shirts.html | /free-design-tools/niche/bachelor-party-shirts |
| 5 | bakery-apparel.html | /free-design-tools/niche/bakery-apparel |
| 6 | bar-apparel.html | /free-design-tools/niche/bar-apparel |
| 7 | barbershop-apparel.html | /free-design-tools/niche/barbershop-apparel |
| 8 | barre-studio-apparel.html | /free-design-tools/niche/barre-studio-apparel |
| 9 | baseball-apparel.html | /free-design-tools/niche/baseball-apparel |
| 10 | basketball-apparel.html | /free-design-tools/niche/basketball-apparel |
| 11 | birthday-shirts.html | /free-design-tools/niche/birthday-shirts |
| 12 | boat-club-apparel.html | /free-design-tools/niche/boat-club-apparel |
| 13 | bootcamp-apparel.html | /free-design-tools/niche/bootcamp-apparel |
| 14 | boxing-gym-apparel.html | /free-design-tools/niche/boxing-gym-apparel |
| 15 | boy-scouts-apparel.html | /free-design-tools/niche/boy-scouts-apparel |
| 16 | brewery-merch.html | /free-design-tools/niche/brewery-merch |
| 17 | cafe-apparel.html | /free-design-tools/niche/cafe-apparel |
| 18 | car-club-apparel.html | /free-design-tools/niche/car-club-apparel |
| 19 | catering-apparel.html | /free-design-tools/niche/catering-apparel |
| 20 | cheerleading-apparel.html | /free-design-tools/niche/cheerleading-apparel |
| 21 | church-apparel.html | /free-design-tools/niche/church-apparel |
| 22 | college-apparel.html | /free-design-tools/niche/college-apparel |
| 23 | company-swag.html | /free-design-tools/niche/company-swag |
| 24 | conference-apparel.html | /free-design-tools/niche/conference-apparel |
| 25 | construction-apparel.html | /free-design-tools/niche/construction-apparel |
| 26 | country-club-apparel.html | /free-design-tools/niche/country-club-apparel |
| 27 | creator-merch.html | /free-design-tools/niche/creator-merch |
| 28 | cruise-apparel.html | /free-design-tools/niche/cruise-apparel |
| 29 | dance-studio-apparel.html | /free-design-tools/niche/dance-studio-apparel |
| 30 | daycare-apparel.html | /free-design-tools/niche/daycare-apparel |
| 31 | dentist-apparel.html | /free-design-tools/niche/dentist-apparel |
| 32 | dispensary-apparel.html | /free-design-tools/niche/dispensary-apparel |
| 33 | doctor-office-apparel.html | /free-design-tools/niche/doctor-office-apparel |
| 34 | dog-trainer-apparel.html | /free-design-tools/niche/dog-trainer-apparel |
| 35 | dog-walker-apparel.html | /free-design-tools/niche/dog-walker-apparel |
| 36 | dropshipping-apparel.html | /free-design-tools/niche/dropshipping-apparel |
| 37 | electrician-apparel.html | /free-design-tools/niche/electrician-apparel |
| 38 | elementary-school-apparel.html | /free-design-tools/niche/elementary-school-apparel |
| 39 | entrepreneur-apparel.html | /free-design-tools/niche/entrepreneur-apparel |
| 40 | event-planner-apparel.html | /free-design-tools/niche/event-planner-apparel |
| 41 | family-reunion-shirts.html | /free-design-tools/niche/family-reunion-shirts |
| 42 | farmers-market-apparel.html | /free-design-tools/niche/farmers-market-apparel |
| 43 | fire-department-apparel.html | /free-design-tools/niche/fire-department-apparel |
| 44 | fishing-apparel.html | /free-design-tools/niche/fishing-apparel |
| 45 | flag-football-apparel.html | /free-design-tools/niche/flag-football-apparel |
| 46 | food-truck-apparel.html | /free-design-tools/niche/food-truck-apparel |
| 47 | fraternity-sorority-apparel.html | /free-design-tools/niche/fraternity-sorority-apparel |
| 48 | functional-fitness-apparel.html | /free-design-tools/niche/functional-fitness-apparel |
| 49 | girl-scouts-apparel.html | /free-design-tools/niche/girl-scouts-apparel |
| 50 | golf-apparel.html | /free-design-tools/niche/golf-apparel |
| 51 | graduation-apparel.html | /free-design-tools/niche/graduation-apparel |
| 52 | gym-apparel.html | /free-design-tools/niche/gym-apparel |
| 53 | gymnastics-apparel.html | /free-design-tools/niche/gymnastics-apparel |
| 54 | high-school-apparel.html | /free-design-tools/niche/high-school-apparel |
| 55 | hiit-gym-apparel.html | /free-design-tools/niche/hiit-gym-apparel |
| 56 | hiking-club-apparel.html | /free-design-tools/niche/hiking-club-apparel |
| 57 | hockey-apparel.html | /free-design-tools/niche/hockey-apparel |
| 58 | hospital-apparel.html | /free-design-tools/niche/hospital-apparel |
| 59 | hvac-apparel.html | /free-design-tools/niche/hvac-apparel |
| 60 | hybrid-athlete-apparel.html | /free-design-tools/niche/hybrid-athlete-apparel |
| 61 | instagram-merch.html | /free-design-tools/niche/instagram-merch |
| 62 | insurance-apparel.html | /free-design-tools/niche/insurance-apparel |
| 63 | kickboxing-apparel.html | /free-design-tools/niche/kickboxing-apparel |
| 64 | lacrosse-apparel.html | /free-design-tools/niche/lacrosse-apparel |
| 65 | landscaping-apparel.html | /free-design-tools/niche/landscaping-apparel |
| 66 | law-firm-apparel.html | /free-design-tools/niche/law-firm-apparel |
| 67 | martial-arts-apparel.html | /free-design-tools/niche/martial-arts-apparel |
| 68 | micro-influencer-merch.html | /free-design-tools/niche/micro-influencer-merch |
| 69 | middle-school-apparel.html | /free-design-tools/niche/middle-school-apparel |
| 70 | mma-apparel.html | /free-design-tools/niche/mma-apparel |
| 71 | mortgage-apparel.html | /free-design-tools/niche/mortgage-apparel |
| 72 | nonprofit-fundraiser.html | /free-design-tools/niche/nonprofit-fundraiser |
| 73 | nurse-apparel.html | /free-design-tools/niche/nurse-apparel |
| 74 | personal-brand-merch.html | /free-design-tools/niche/personal-brand-merch |
| 75 | personal-trainer-apparel.html | /free-design-tools/niche/personal-trainer-apparel |
| 76 | pest-control-apparel.html | /free-design-tools/niche/pest-control-apparel |
| 77 | pet-business-apparel.html | /free-design-tools/niche/pet-business-apparel |
| 78 | pickleball-apparel.html | /free-design-tools/niche/pickleball-apparel |
| 79 | pilates-studio-apparel.html | /free-design-tools/niche/pilates-studio-apparel |
| 80 | pizza-shop-apparel.html | /free-design-tools/niche/pizza-shop-apparel |
| 81 | plumbing-apparel.html | /free-design-tools/niche/plumbing-apparel |
| 82 | print-on-demand-platform.html | /free-design-tools/niche/print-on-demand-platform |
| 83 | print-on-demand-side-hustle.html | /free-design-tools/niche/print-on-demand-side-hustle |
| 84 | print-on-demand-tools-2026.html | /free-design-tools/niche/print-on-demand-tools-2026 |
| 85 | private-school-apparel.html | /free-design-tools/niche/private-school-apparel |
| 86 | real-estate-apparel.html | /free-design-tools/niche/real-estate-apparel |
| 87 | rec-league-apparel.html | /free-design-tools/niche/rec-league-apparel |
| 88 | restaurant-apparel.html | /free-design-tools/niche/restaurant-apparel |
| 89 | rock-climbing-apparel.html | /free-design-tools/niche/rock-climbing-apparel |
| 90 | roofing-apparel.html | /free-design-tools/niche/roofing-apparel |
| 91 | running-club-apparel.html | /free-design-tools/niche/running-club-apparel |
| 92 | sales-team-apparel.html | /free-design-tools/niche/sales-team-apparel |
| 93 | salon-apparel.html | /free-design-tools/niche/salon-apparel |
| 94 | school-spirit-wear.html | /free-design-tools/niche/school-spirit-wear |
| 95 | side-hustle-apparel.html | /free-design-tools/niche/side-hustle-apparel |
| 96 | smoke-shop-apparel.html | /free-design-tools/niche/smoke-shop-apparel |
| 97 | soccer-apparel.html | /free-design-tools/niche/soccer-apparel |
| 98 | softball-apparel.html | /free-design-tools/niche/softball-apparel |
| 99 | spa-apparel.html | /free-design-tools/niche/spa-apparel |
| 100 | spin-studio-apparel.html | /free-design-tools/niche/spin-studio-apparel |
| 101 | startup-swag.html | /free-design-tools/niche/startup-swag |
| 102 | summer-camp-apparel.html | /free-design-tools/niche/summer-camp-apparel |
| 103 | swim-team-apparel.html | /free-design-tools/niche/swim-team-apparel |
| 104 | teacher-apparel.html | /free-design-tools/niche/teacher-apparel |
| 105 | tech-company-apparel.html | /free-design-tools/niche/tech-company-apparel |
| 106 | tennis-apparel.html | /free-design-tools/niche/tennis-apparel |
| 107 | tiktok-merch.html | /free-design-tools/niche/tiktok-merch |
| 108 | track-field-apparel.html | /free-design-tools/niche/track-field-apparel |
| 109 | university-apparel.html | /free-design-tools/niche/university-apparel |
| 110 | veterinary-apparel.html | /free-design-tools/niche/veterinary-apparel |
| 111 | volleyball-apparel.html | /free-design-tools/niche/volleyball-apparel |
| 112 | winery-apparel.html | /free-design-tools/niche/winery-apparel |
| 113 | wrestling-apparel.html | /free-design-tools/niche/wrestling-apparel |
| 114 | yoga-studio-apparel.html | /free-design-tools/niche/yoga-studio-apparel |
| 115 | youth-group-apparel.html | /free-design-tools/niche/youth-group-apparel |
| 116 | youth-sports-apparel.html | /free-design-tools/niche/youth-sports-apparel |
| 117 | youtuber-merch.html | /free-design-tools/niche/youtuber-merch |
Steps
1 Create the niche/ directory
Create a niche/ subfolder inside the free-design-tools directory:
2 Upload all 117 niche HTML files
Upload all .html files from the niche folder:
Important: Asset paths inside niche pages use ../assets/ (one level up). Make sure the assets/ folder is already uploaded at the free-design-tools level.
3 Verify niche pages load
Spot-check a few niche pages to confirm they work:
https://shops.beargrips.com/free-design-tools/niche/gym-apparel.htmlhttps://shops.beargrips.com/free-design-tools/niche/church-apparel.htmlhttps://shops.beargrips.com/free-design-tools/niche/pickleball-apparel.html- Logo + favicon loads on every page (from
../assets/) - Tool links point to
../background-remover.htmletc. (correct path) - CTA buttons link to
https://shops.beargrips.com - Check mobile responsiveness
4 Add all 117 niche pages to sitemap
Add all niche page URLs to sitemap.xml. Each gets priority 0.6:
Full list of URLs in the table above. All follow the same pattern.
5 Nginx — serve niche/ as static files
If nginx proxies everything to the Node app, add a location block for the niche directory:
This one block covers both the main tools pages AND the niche subdirectory.
Product Catalog — 85 SEO Pages
85 static HTML pages competing with Apliiq, Printify, and Printful for "custom [product] print on demand" keywords. All pages are pre-built with full SEO — just upload to the server.
What's Included
| Type | Count | Example URL |
|---|---|---|
| Catalog Hub | 1 | /products/index.html |
| Category Pages | 11 | /products/custom-t-shirts.html, /products/custom-hoodies.html |
| Brand Pages | 7 | /products/brands/bella-canvas.html, /products/brands/champion.html |
| Audience Pages | 3 | /products/womens.html, /products/mens.html, /products/youth.html |
| Product Pages | 63 | /products/products/airlume-cotton-athletic-tee.html |
| XML Sitemap | 1 | /products/sitemap-products.xml |
| Product Images | 85 | /products/images/*.png (Bear Grips proprietary) |
| Brand Logos | 6 | /products/images/brands/*.png |
SEO Features (Every Page)
- Unique title tags + meta descriptions targeting "custom [product] print on demand no minimum"
- BreadcrumbList schema (JSON-LD) on all pages
- Product schema (JSON-LD) on all 63 individual product pages
- FAQPage schema (JSON-LD) on hub page — 10 Q&As with crosslinks
- Open Graph + Twitter Card meta tags for social sharing
- Canonical URLs on every page
- Internal crosslinks between category, brand, audience, product, and free tools pages
- Print method section: DTG, DTF, Embroidery with product counts
- All-inclusive pricing messaging: "FREE Shipping + Printing Included + Unlimited Colors"
Deployment Steps
1 Create /products/ directory on server
Create the products directory structure on the DO server:
2 Upload all files from built-pages/catalog/
Upload the entire catalog build output to the server:
Total files: ~100 HTML + 85 images + 6 logos + assets + 1 XML sitemap
3 Nginx location block for /products/
Add this BEFORE the proxy_pass catch-all in the shops nginx config:
Then reload nginx:
4 Add product sitemap to robots.txt
Add the product catalog sitemap reference to robots.txt:
5 Verify pages load correctly
Test these URLs after deployment:
https://shops.beargrips.com/products/— Catalog hub with all categories, brands, featured productshttps://shops.beargrips.com/products/custom-t-shirts.html— Category pagehttps://shops.beargrips.com/products/brands/bella-canvas.html— Brand pagehttps://shops.beargrips.com/products/womens.html— Audience pagehttps://shops.beargrips.com/products/products/airlume-cotton-athletic-tee.html— Product pagehttps://shops.beargrips.com/products/sitemap-products.xml— XML sitemap- Verify: product images load, Bear Grips logo shows, "FREE Shipping" badges visible
- Verify: mobile responsive layout works
- Check View Source → JSON-LD schema present in <head>
6 Add internal link from shops homepage
Add a "Browse Catalog" or "Product Catalog" link somewhere visible on shops.beargrips.com homepage — nav bar, footer, or hero section. Link to:
This passes link equity from the homepage to all 85 catalog pages.
File Structure on Server
Programmatic SEO Pages (2,100+)
All pSEO pages are already live on Cloudflare Pages. No dev action needed for these — they're static HTML hosted separately.
Page Types & Counts
| Phase | Type | Pages | Example URL |
|---|---|---|---|
| 1 | Resource Pages (ideas, checklists, guides) | 354 | /resources/custom-gym-apparel-ideas.html |
| 2 | Competitor Comparisons | 35 | /comparisons/bear-grips-vs-printful.html |
| 3 | POD Glossary | 160 | /glossary/print-on-demand.html |
| 4 | Profit Calculator | 1 | /tools/profit-calculator.html |
| 5 | Affiliate / Side Hustle | 8 | /affiliate/best-pod-side-hustles-2026.html |
| 6 | Occasion / Seasonal | 17 | /occasions/fathers-day-custom-apparel.html |
| 7 | Vendor Spotlight Gallery | 8 | /vendors/gallery.html |
| 8 | Fundraising Content | 11 | /fundraising/church-fundraiser-apparel.html |
| 9 | Deals / Coupons | 1 | /deals/ |
| 10 | Niche Deep-Dives (sub-niches) | 620 | /tools/niche/boxing-gym-apparel.html |
| 11 | Product × Niche | 821 | /product-niche/hoodies-for-yoga-studio-apparel.html |
| 12 | How-To Guides | 118 | /how-to/how-to-start-a-gym-apparel-merch-business.html |
| TOTAL | 2,163+ |
What These Pages Do
- Target long-tail keywords — "custom yoga studio apparel ideas", "bear grips vs printful", "how to start a gym merch business"
- Drive organic traffic — each page targets a unique keyword with real content, not thin/duplicate
- Convert visitors — every page has CTAs to sign up at shops.beargrips.com
- Build authority — glossary, comparisons, and how-to guides establish Bear Grips as the category expert
Hosting Details
- Host: Cloudflare Pages (free tier, unlimited bandwidth)
- Project: bear-grips-pseo
- Source:
built-pages/directory in the repo - Architecture: Static HTML generated from JSON data files + Node.js generator scripts
- Sitemaps: Each phase has its own XML sitemap in its directory
Deploy pSEO Pages to shops.beargrips.com Subfolders
2,100+ static HTML pages need to be served as subfolders on shops.beargrips.com (e.g., /resources/, /comparisons/, /tools/). This is better for SEO than a subdomain — all link equity and authority stays on the main domain.
Step-by-Step Deploy
1 Create the pSEO directory on the server
2 Upload pSEO files to the server
KJ will provide you with the built-pages/ folder (47MB, 2,100+ HTML files). Copy the entire contents into /var/www/pseo-pages/.
The folder structure should look like this after upload:
Upload method (from your local machine):
3 Update the shops nginx config to serve pSEO subfolders
Edit the existing shops.beargrips.com nginx config — add location blocks for each pSEO subfolder ABOVE the existing React catch-all location block.
Add these location blocks BEFORE the main location / block that serves the React app:
Important: These location blocks must appear BEFORE the React app's catch-all location / { try_files $uri /index.html; } block. Otherwise nginx will send pSEO URLs to the React app instead of serving the static files.
4 Test and reload nginx
5 Verify it works
Test these URLs in your browser:
| URL | Expected |
|---|---|
https://shops.beargrips.com/deals/ | Deals & coupons page (dark theme, Bear Grips branding) |
https://shops.beargrips.com/glossary/print-on-demand.html | Glossary page for "Print on Demand" |
https://shops.beargrips.com/tools/profit-calculator.html | Interactive profit calculator |
https://shops.beargrips.com/how-to/how-to-start-a-gym-apparel-merch-business.html | How-to guide for gym merch |
https://shops.beargrips.com/product-niche/hoodies-for-yoga-studio-apparel.html | Custom hoodies for yoga studios |
https://shops.beargrips.com/comparisons/bear-grips-vs-printful.html | Bear Grips vs Printful comparison |
If any page returns 404, check that files were copied correctly to /var/www/pseo-pages/ and that the nginx location blocks are ABOVE the React catch-all.
Also verify the React app still works: https://shops.beargrips.com/ should load the normal shops homepage.
Important Notes
- DO NOT create a separate nginx config file — add the location blocks to the existing shops.beargrips.com config
- Order matters: pSEO location blocks MUST be above the React catch-all
location /block - These are static HTML files — no Node.js, no database, no build step needed
- No new SSL cert needed — pages are served under the existing shops.beargrips.com SSL cert
- Total size: ~47MB (2,100+ HTML files + product images)
- Each folder has its own XML sitemap — KJ will submit these to Google Search Console after deploy
- If you need to update pages later, just replace the files in /var/www/content/
Security Hardening
Pre-launch security checklist for shops.beargrips.com. Stripe handles real money, vendor accounts hold business data — this is not optional.
1. Stripe Payment Security
Stripe handles PCI compliance for card data, but your server-side integration must be airtight.
1 Webhook Signature Verification
Every Stripe webhook must be verified with stripe.webhooks.constructEvent() using your webhook signing secret. Without this, anyone can fake a payment/subscription event by sending a POST to your webhook endpoint.
2 Server-Side Price Validation
Never trust prices from the frontend. Always fetch product base prices from your database server-side when calculating orders. A malicious user can modify frontend JS to send $0 prices.
3 Stripe API Key Security
- Use restricted API keys with only the permissions your app needs (charges, subscriptions, customers)
- Store keys in
.env— never in source code or frontend JS - Publishable key (pk_live_) = safe for frontend. Secret key (sk_live_) = server only, NEVER exposed
- Rotate keys immediately if you suspect any exposure
4 Use Stripe Checkout or Elements
Never handle raw card numbers on your server. Use Stripe Checkout (hosted payment page) or Stripe Elements (embedded card form). Both keep card data on Stripe's servers — your server never touches it. This is required for PCI compliance.
2. Authentication & Session Security
1 Secure Session Cookies
Session tokens must use all three flags:
Never store session tokens in localStorage or sessionStorage — these are readable by any JS on the page, including injected scripts.
2 CSRF Protection
Add CSRF tokens to all state-changing forms and API endpoints (login, signup, plan changes, shop edits, Stripe actions). Libraries like csurf (Express) handle this.
3 Password Security
- Hash passwords with bcrypt (cost factor 12+) or argon2
- Never store plaintext, MD5, or SHA hashes
- Enforce minimum 8 characters
- Rate limit login: 5 failed attempts → 15 min lockout per IP + per account
4 Session Management
- Auto-logout after 30-60 minutes of inactivity
- Invalidate old sessions on password change
- Generate new session ID after login (prevent session fixation)
3. Server Security Headers (Nginx)
Add these headers to your Nginx server block for shops.beargrips.com. They prevent clickjacking, XSS, MIME sniffing, and enforce HTTPS.
4. DDoS & Bot Protection
Cloudflare is already live on shops.beargrips.com. No setup needed — just be aware that shops, content, and directory subdomains are proxied through Cloudflare.
5. Rate Limiting on API Routes
Rate limit all API endpoints to prevent brute force and abuse. The directory app already has this pattern — apply it to shops.
6. Input Validation & Injection Prevention
SQL / NoSQL Injection
- Always use parameterized queries — never concatenate user input into database queries
- Use an ORM (Prisma, Sequelize, Mongoose) which parameterizes by default
- Validate and sanitize all inputs: vendor names, shop descriptions, product titles, profile fields
XSS (Cross-Site Scripting)
- React escapes HTML output by default — but never use
dangerouslySetInnerHTMLwith user content - Sanitize any user-generated content displayed on public pages (shop names, product descriptions)
- Use a library like
DOMPurifyif you must render HTML from user input
File Upload Security — Use Cloudflare R2
R2 Bucket — Ready to Use
Add to .env
Node.js Setup
Important Notes
- Upload from Node.js backend only — never expose R2 keys to the browser
- Store the returned R2 key (e.g.
vendor123/abc-uuid.png) in MongoDB alongside the product/vendor record - Default product mockups still come from Printify/Printful CDN — R2 is only for vendor-uploaded files
- Make sure
.envis in.gitignore
Upload Flow
- Validate file type server-side via magic bytes (not just extension) — allow: JPEG, PNG, WebP, SVG
- Enforce 10MB max per file
- Rename to UUID:
{vendorId}/{uuid}.{ext}— never use original filename - Upload to R2 from the Node.js backend (never direct from browser to R2)
- Store the R2 key/URL in DB alongside the product/vendor record
- Serve images with proper MIME type +
Cache-Controlheaders - Never execute uploaded files
Upload Types & Limits
| Upload Type | Max Files | Max Size | Notes |
|---|---|---|---|
| Vendor logo/design | ~5-6 per vendor | 10MB each | Used for DFY monthly designs |
| Custom product mockups | 4 per product | 10MB each | Replaces default front/back mockups |
Volume Estimates
| Scale | Vendors | Est. Files | Est. Storage | R2 Cost |
|---|---|---|---|---|
| Now | 20 DFY | ~2,000 | ~4 GB | Free |
| 100 vendors | Mixed tiers | ~20,000 | ~40 GB | ~$0.45/mo |
| 1,000 vendors | Mixed tiers | ~200,000+ | ~400 GB | ~$5.85/mo |
7. Data Protection
Environment Variables
- All API keys, DB credentials, Stripe keys, secrets in
.env— never committed to git - Verify
.gitignoreincludes:.env,.env.local,.env.production - Use different keys for development vs production
Database Backups
- Automated daily backups of all vendor data, orders, account info
- Store backups off-server (S3, Backblaze B2, or DO Spaces)
- Test restore process at least once before launch
- Set up weekly backup verification (restore to a test DB and confirm data integrity)
robots.txt — Block Sensitive Routes
8. Monitoring & Alerts
Error Logging
- Sentry (free tier) — account created. Login: proshops@beargrips.com / fgr89^Q&6fTr
- Log in → Create a Node.js project → copy the DSN key
- Install:
npm install @sentry/node - Add to Express app (before routes):
const Sentry = require("@sentry/node"); Sentry.init({ dsn: "YOUR_DSN_KEY" }); app.use(Sentry.Handlers.requestHandler()); - Add after routes:
app.use(Sentry.Handlers.errorHandler()); - Log all failed login attempts with IP and timestamp
- Log all Stripe webhook failures
- Never log sensitive data (passwords, card numbers, full API keys)
Quick Verification Checklist
After implementing, run these checks:
| # | Check | How to Test |
|---|---|---|
| 1 | HTTPS forced | Visit http://shops.beargrips.com — should redirect to https:// |
| 2 | Security headers | Run securityheaders.com — aim for A or A+ grade |
| 3 | Stripe webhooks verified | Send a test webhook from Stripe dashboard → confirm it's processed. Then send a fake POST to your webhook URL → confirm it's rejected |
| 4 | Rate limiting works | Hit /api/auth/login 6+ times rapidly → should get 429 on attempt 6 |
| 5 | XSS blocked | Try entering <script>alert(1)</script> as a shop name → should be escaped/rejected |
| 6 | No directory listing | Visit a directory URL like /api/ directly → should get 404/403, not a file listing |
| 7 | SSL cert valid | Run ssllabs.com/ssltest — aim for A grade |
| 8 | Sensitive routes blocked | Visit /admin/, /dashboard/ as logged-out user → should redirect to login, not show content |
Blog & URL Architecture
URL structure for all content pages on shops.beargrips.com. This defines where every page lives so internal links work correctly across the entire site.
Complete URL Map
Resources (Curated — keep clean)
| URL | Content | Status |
|---|---|---|
| /resources/ | Resources hub page | LIVE |
| /resources/design-services/ | Design services overview | LIVE |
| /resources/design-services/graphic-designer-logo | $99 logo design service | LIVE |
| /resources/design-services/file-formatting | $29 file formatting service | LIVE |
| /resources/free-design-tools/ | 12 free tools hub + individual tool pages | READY — needs deploy |
| /resources/free-design-tools/{tool-name} | Individual tool pages (12 total) | READY — needs deploy |
/blog/ — All SEO & Niche Content (3,800+ pages)
The blog holds all programmatic niche content. This keeps /resources/ clean while giving Google thousands of indexable pages. Structure:
| URL Pattern | Content Type | Count | Status |
|---|---|---|---|
| /blog/ | Blog index/hub page | 1 | Needs creation |
| /blog/tools/{tool}-for-{niche}/ | Free tool × niche SEO pages | 3,264 | BUILT — ready to deploy |
| /blog/services/logo-design-{niche}/ | $99 logo design × niche pages | 272 | BUILT — ready to deploy |
| /blog/services/file-formatting-{niche}/ | $29 file formatting × niche pages | 272 | BUILT — ready to deploy |
| /blog/{article-slug}/ | Written blog articles (how-to, guides) | 3 | WRITTEN — in SEO/06-blog-posts.md |
Other Existing Content (Separate Deploy)
| URL Pattern | Content Type | Count |
|---|---|---|
| /catalog/ | Product catalog pages | 85 |
| /comparisons/ | Bear Grips vs competitor pages | 35 |
| /{niche}/ | Top-level niche landing pages (CrossFit, Yoga, etc.) | 6 planned |
Server Setup
Nginx Configuration
Add these location blocks to the existing shops nginx config. These serve static HTML files from the blog and tools directories while keeping the React app running for everything else.
Internal Linking Rules
- Tool×niche pages link to: the actual free tool, design services ($99 + $29), pro shop signup, other free tools
- Service×niche pages link to: the service order page, free tools (DIY alternative), the other paid service, pro shop signup
- Free tool pages link to: design services upsell, pro shop CTA
- Blog articles link to: relevant niche pages, /how-it-works, /pricing, /catalog
The Funnel
All content funnels traffic toward these conversion points:
- Google → Blog niche page (SEO entry point)
- Blog page → Free tool (user engages with tool)
- Free tool → Design services (if tool didn't work: $29 formatting or $99 logo)
- Any page → Pro Shop signup (the real conversion: free or paid plan)
Sitemap
Once deployed, add all blog URLs to the sitemap. With 3,800+ pages, split into multiple sitemap files:
- sitemap-blog-tools.xml — 3,264 tool×niche URLs
- sitemap-blog-services.xml — 544 service×niche URLs
- sitemap-blog-articles.xml — written blog posts
- sitemap-index.xml — references all sub-sitemaps
Content Already Built (File Locations)
| Content | Local Path | File Count |
|---|---|---|
| Free tools (hub + 12 tools + assets) | built-pages/tools/ | 14 files + assets/ |
| Niche hub pages | built-pages/tools/niche/ | 271 files |
| Tool × niche pages | built-pages/tools/niche/tool-pages/ | 3,264 files |
| Logo design × niche pages | built-pages/tools/niche/services/ | 272 files |
| File formatting × niche pages | built-pages/tools/niche/services/ | 272 files |
| Blog articles (markdown) | SEO/06-blog-posts.md | 3 articles |
| Product catalog | built-pages/catalog/ | 85 files |
| Comparison pages | built-pages/comparisons/ | 35 files |
LLM Integration API Requirements
Staff-level API endpoints for Claude to perform high-volume backend work — product uploads, vendor onboarding, SEO, blog content — without touching the codebase or server.
1. Core Principles
1 PATCH, Never PUT
Every update endpoint must use PATCH — updating only the fields sent in the request. PUT replaces the entire object with whatever is submitted, which causes existing data to be overwritten or lost. This is the single most important technical rule in this document. No update route should accept a full object replacement.
2 Auto-Publish by Default
Product creation (POST /api/products) should auto-publish immediately by default. This is essential for high-volume batch runs — requiring a separate publish call per product adds hours of overhead across hundreds of items. An optional draft: true parameter should be supported for cases where a hold is needed.
3 Staff Role Scoping
Claude authenticates using a scoped API key (see Section 2). It inherits staff-level permissions exactly — no more, no less. No admin routes should be exposed to this layer.
4 Snapshot Before Bulk Operations
Any operation touching more than one existing record must automatically snapshot the affected records to a restore table before executing. This happens at the API level — not triggered by Claude, not optional. Snapshots retained for minimum 30 days.
5 No Destructive Wildcards
Bulk delete and bulk edit endpoints must require an explicit array of record IDs or a named filter. Wildcard operations (delete all, update all) are not permitted.
6 Idempotency on Creates
All POST (create) endpoints must accept an optional Idempotency-Key header. If the same key is sent twice (e.g., network retry), return the original response instead of creating a duplicate.
2. Authentication
Claude is a headless API client — it does not run in a browser and cannot access localStorage, cookies, or session storage. Authentication must work for a non-browser client making direct HTTP calls.
Recommended: Scoped API Key
Generate a long-lived API key tied to a staff-level account. Passed in every request header:
- Key inherits the staff account's permissions — no additional role logic needed
- Revocable instantly by the owner if needed
- No token refresh, no expiry management, no re-authentication mid-session
- One key per access tier (staff key, admin key)
Requirements
- Never use
credentials: 'include'(cookie-based auth) — Bearer token only - All endpoints operate under staff-level permissions unless noted as admin-tier
- A
GET /api/healthendpoint should return status, tier, and version — used to verify connectivity before starting batch work
3. Standard Response Format
All endpoints must return a consistent JSON envelope. Inconsistent response shapes cause parsing failures across hundreds of sequential calls.
Success Response
Error Response
Standard Error Codes
| Code | Meaning |
|---|---|
UNAUTHORIZED | Invalid or expired API key |
FORBIDDEN | Valid key but insufficient tier |
NOT_FOUND | Entity does not exist |
DUPLICATE_PRODUCT | Product with same base name exists for vendor |
VALIDATION_ERROR | Missing or invalid fields in request body |
SNAPSHOT_REQUIRED | Bulk operation blocked — snapshot system unavailable |
CONFIRM_REQUIRED | Destructive operation missing confirm parameter |
PROVIDER_ERROR | Upstream error from Printify/Printful/Apliiq |
4. Catalog Endpoints
The catalog is the source of truth for all product templates. Claude needs read access to list and inspect catalog products, and targeted write access for variant filtering only.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/catalog | List all catalog products. Must return the full catalog with no artificial cap — support pagination (page + limit) or limit=all. |
| GET | /api/catalog/:catalogId | Get single catalog product — sizes, description, pricing, provider data |
| GET | /api/catalog/:catalogId/printify-data | Get Printify cached variant, color, and image data for a catalog item |
| PATCH | /api/catalog/:catalogId/printify-data | Update only the printify_product_data field — used for variant filtering before upload. Must not touch any other catalog fields. |
The catalog's availableSizes and availableColors fields must return plain string arrays. Both fields are used directly in product creation payloads.
5. Product Endpoints
Claude needs to do everything a staff member does on the product side — read the catalog, create products from catalog items, publish them, edit them, and delete them. The catalog is made up of products from three POD providers (Printify, Printful, Apliiq). A single batch can mix products from all three.
What Claude Needs to Do
- Read the full product catalog — browse all available products across all three providers, see pricing, variants, colors, sizes
- Create products on a vendor's shop from catalog items — single or batch (up to 15–50 at a time for DFY VIP). Auto-publish by default, with option to hold as draft.
- Check for duplicates before creating — does this product name already exist for this vendor?
- Poll job status — product creation is async, Claude needs to check if it completed or failed
- Read a vendor's existing products — list all, view details, get edit data
- Edit existing products — PATCH specific fields only, never full replacement
- Delete products — with a confirmation step for live published items
- Publish draft products when ready
Mixed-Provider Batches
A batch of 15 products may include items from Printify, Printful, and Apliiq mixed together. The API should abstract the provider differences — same way the frontend already works. Claude picks a catalog item, the API handles routing to the right provider. If an item in a batch fails (e.g., Printful mockup timeout), that shouldn't block the other items from completing.
6. Media Library
Two actions map to what a staff member does in the UI: upload an image to the media library, then click it to attach it to the product editor. Both need API equivalents.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/media/vendor/:vendorId | List all assets in a vendor's media library. Returns: assetId, url, filename, width, height. No cap. |
| GET | /api/media/vendor/:vendorId?search=X | Search media library by filename. |
| GET | /api/media/:assetId | Get a single asset by ID. |
| POST | /api/media/upload | Upload image to vendor's media library. Accepts multipart/form-data or base64. Returns assetId, url, width, height. |
| POST | /api/media/:assetId/select | Attach asset to product editor — programmatic equivalent of clicking an image in the UI. Returns full asset reference: assetId, url, width, height, sourceAssetId. |
| DELETE | /api/media/:assetId | Delete a media asset. Scoped to vendor's own library only. |
The assetId maps to sourceAssetId in Printify/Printful payloads. The url maps to designImage and previewUrl. The select endpoint must return all fields needed to complete a product creation payload in a single call.
7. Vendor Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/vendors/:vendorId | Basic vendor info — plan type (free/VIP), store slug, business name |
| GET | /api/vendors/:vendorId/settings | Read vendor store settings |
| PATCH | /api/vendors/:vendorId/settings | Update specific store settings — header, logo, OG metadata, social links. PATCH only. Required for DFY VIP. |
| GET | /api/vendors/:vendorId/layout | Read vendor storefront layout |
| PATCH | /api/vendors/:vendorId/layout | Update storefront layout — sections, featured products, section order, banner. PATCH only. Required for DFY VIP. |
| GET | /api/vendors/:vendorId/logo | Get the vendor's uploaded logo/design files. Returns URL(s) and metadata. |
| GET | /api/vendors/:vendorId/categories | List product categories on the vendor's storefront |
| POST | /api/vendors/:vendorId/categories | Create a new product category |
| PATCH | /api/vendors/:vendorId/categories/:categoryId | Update a category name or contents |
| DELETE | /api/vendors/:vendorId/categories/:categoryId | Delete a category — only if it contains no products |
| PATCH | /api/vendors/:vendorId/categories/reorder | Set display order of categories on storefront |
Vendor Onboarding
The platform has an existing backend onboarding submission page. Claude needs to read what the vendor submitted, download any files they uploaded (logos, designs), do the work on their shop, and mark the onboarding as complete.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/vendors/:vendorId/onboarding | Read the vendor's onboarding submission — business info, preferences, plan type, and list of uploaded files. |
| GET | /api/vendors/:vendorId/onboarding/files | List all files the vendor uploaded during onboarding (logos, designs, assets). Returns filename, URL, file type, dimensions. |
| GET | /api/vendors/:vendorId/onboarding/files/:fileId | Download a specific uploaded file by ID. |
| PATCH | /api/vendors/:vendorId/onboarding | Mark onboarding as complete (or mark individual steps complete). PATCH only. |
8. SEO & Metadata Endpoints
Claude needs to read and write SEO fields on products, pages, and blog posts — either as part of creating new content or as a standalone SEO cleanup task.
What Claude Needs to Do
- Read current SEO fields on any product, page, or blog post — meta title, meta description, slug, alt text, canonical URL, structured data
- Write/update SEO fields — PATCH only, only the fields being updated
Scope: SEO fields on content only. Not: sitemap config, robots.txt, domain settings, or server-level SEO infrastructure.
9. Blog & Content Endpoints
Claude needs full create/edit/delete access to blog posts, categories, and site pages. Same content management a staff member does, just through the API.
What Claude Needs to Do
- Blog posts — list, read, create, edit (PATCH), delete individual posts
- Blog categories — list, create, edit, delete (only if empty)
- Site pages — list, create, edit (PATCH), delete
- Bulk operations — bulk edit or bulk delete posts by explicit ID array (no wildcards, no "delete all")
Safety rule: Deleting an entire blog, wiping all categories, or removing all pages in a single call must not be possible. Bulk operations require explicit ID arrays.
10. Snapshot & Revert System
Safety net for bulk operations. Any API call that modifies or deletes more than one existing record must auto-snapshot before executing. This is at the infrastructure level — Claude does not trigger it.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/snapshots | List available snapshots — timestamp, operation type, record count, entity type |
| GET | /api/snapshots/:snapshotId | View full snapshot contents before reverting |
| POST | /api/snapshots/:snapshotId/revert | Restore all records to pre-operation state |
| DELETE | /api/snapshots/:snapshotId | Manually delete a snapshot (admin-tier only) |
Retention: minimum 30 days, then auto-purge. Each snapshot records: timestamp, operation label, affected record IDs, and full previous state. Even a bulk operation touching 10,000 records is fully recoverable.
11. SEO Infrastructure Endpoints
These endpoints give Claude visibility into the site's SEO health and the ability to generate sitemaps. Unlike Section 8 (SEO metadata on content), these are infrastructure-level — they report on the platform's indexing status and expose data needed for sitemap generation.
Sitemap Data Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/sitemap/shops | Return all LIVE vendor shops with slug, lastmod date, and published product count. Used to build sitemap-shops.xml. |
| GET | /api/sitemap/products | Return all PUBLISHED products across all live vendors — product ID, vendor slug, lastmod date. Paginated (limit/offset). Used to build sitemap-products.xml. |
| GET | /api/sitemap/stats | Summary counts: total live shops, total published products, total static pages, total blog posts. One call to see the full picture. |
Response Format — /api/sitemap/shops
Response Format — /api/sitemap/products
SEO Health Endpoint
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/seo/health | Full SEO status report — Claude calls this to audit the site without manual crawling. |
Response Format — /api/seo/health
Why this endpoint matters: Instead of crawling the site externally (which can miss JS-rendered content), Claude calls one endpoint and gets a complete picture of what's working. This replaces the manual audit we did on Mar 27 — catches problems before Google does.
Vendor List Endpoint (needed for sitemap + audits)
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/vendors | List all vendors with basic info: vendorId, slug, businessName, plan, status (live/draft), productCount, createdAt. Paginated. Needed for sitemap generation and SEO audits across all vendors. |
| GET | /api/vendors?status=live | Filter to only live/published vendors — the ones that should appear in sitemaps. |
Note: Section 7 currently only has /api/vendors/:vendorId (single vendor lookup). This adds the list-all endpoint needed for sitemap generation and cross-vendor SEO audits.
12. Rate Limits
These can be adjusted later — the important thing is they exist from day one.
| Limit | Recommended Value |
|---|---|
| Requests per second | 10 |
| Requests per minute | 300 |
| Max concurrent async jobs | 20 |
| Max items per batch create | 50 |
| Max items per bulk-delete | 100 (with confirm_count) |
| Max items per bulk-patch | 100 |
Rate limit responses should return HTTP 429 with a Retry-After header (seconds).
13. Access Summary
| Claude CAN Do | Claude CANNOT Do |
|---|---|
| Read full catalog and vendor stores | Touch the codebase or server |
| Create and auto-publish products (single or batch) | Delete an entire store |
| Browse, search, and select from media library | Delete an entire blog or category structure |
| Upload new files to vendor media library | Use PUT (full object replacement) on any endpoint |
| PATCH existing products when instructed | Run wildcard bulk operations without explicit IDs |
| Delete products — with confirm param for live items | Access admin-only routes |
| Bulk delete/edit with explicit ID arrays | Execute a bulk operation without auto-snapshot |
| Write SEO metadata on any product, page, or post | Override existing vendor customizer products |
| Create, edit, delete blog posts and pages | Modify the codebase, deploy code, or access the server |
| Update vendor store settings and layout (DFY VIP) | Create or modify user accounts or permissions |
| Manage vendor product categories | Access payment or billing data |
| Read onboarding submissions, download files, mark complete | Bypass rate limits or confirmation requirements |
| Revert bulk operations via snapshot system | Call any endpoint not listed in this document |
| Generate sitemaps from /api/sitemap/* data | Modify server-level sitemap or robots.txt files directly |
| Run SEO health checks via /api/seo/health | Change SSR/prerender configuration |
| List all vendors and filter by status | Create or delete vendor accounts |
14. Two-Tier Access Model
Build with two distinct access modes from the start to avoid retrofitting role logic later.
Staff Tier — Team Access
- Full catalog read — all products, no cap
- Create and auto-publish new products (single + batch)
- Read vendor stores, products, settings, layout
- Upload files to vendor media library
- PATCH edits on products, settings, layout
- Delete products — with confirm param for live items
- SEO fields on any product, page, or post — PATCH only
- Create, edit, delete individual blog posts and pages
- Create and manage vendor product categories
- Read onboarding submissions, download files, mark complete
- Read collection templates
- Cannot edit existing vendor products unless instructed
- Cannot touch server or codebase
Admin Tier — Owner Access Only
- Everything in staff tier
- Bulk edits across multiple vendors
- Bulk delete with explicit ID arrays
- Cross-vendor batch operations
- Snapshot and revert access
- Still cannot delete a store or full blog
- Still PATCH only — no full object replacement
- Every bulk operation auto-snapshots before executing
Tier is determined by the key's associated account role — no separate credentials needed. Admin-tier endpoints return 403 when called by a staff-tier key.
15. Open Question
Blog Post Scoping
Clarify: Are blog posts global (platform-level) or per-vendor? If per-vendor, blog endpoints need to accept a vendorId filter.
16. Implementation Priority
Not everything needs to ship at once. Recommended build order based on what unblocks the most staff work first:
1 Phase 1 — Core Product Workflow
- Authentication (API key system)
- Health endpoint
- Catalog read endpoints
- Product CRUD (single create + poll + publish)
- Media library (list, upload, select)
- Product exists check
- Standard response envelope
2 Phase 2 — DFY VIP Workflow
- Batch product creation + polling
- Vendor settings and layout PATCH
- Vendor category management
- Vendor onboarding (read submissions, download files, mark complete)
- Vendor logo retrieval
3 Phase 3 — Content & SEO
- Blog CRUD (posts, categories, pages)
- SEO read/write endpoints
- SEO audit endpoint
- Bulk blog operations
4 Phase 4 — Safety & Scale
- Snapshot system (auto-snapshot + revert)
- Bulk delete with confirmation
- Bulk patch
- Rate limiting
- Idempotency key support
5 Phase 5 — SEO Infrastructure & Monitoring
- Vendor list endpoint (
GET /api/vendorswith status filter) - Sitemap data endpoints (shops, products, stats)
- SEO health endpoint (SSR status, meta coverage, sitemap counts)
- Enables Claude to auto-generate sitemaps and run SEO audits via API instead of external crawling