Task Tracker

Click a task to mark it complete. Progress saves in your browser automatically.

0 of 0 tasks complete

Bear Grips Dev Hub

Everything Dav needs — directory deployment + SEO setup for shops.beargrips.com. Updated March 2026.

PRIORITY #1 — Deploy Gym Directory

New Next.js app on the existing DO server (167.172.23.233). DNS is already pointed. Click "Deploy Directory" in the sidebar to start.

2,100+ pSEO Pages — ALREADY LIVE

Programmatic SEO pages are deployed on Cloudflare Pages at bear-grips-pseo.pages.dev. No dev action needed — just FYI. Click "pSEO Overview" in sidebar for details.

All Tasks

DEPLOY NOW

Deploy Directory App

Upload, build, PM2, nginx, SSL for directory.beargrips.com. Full step-by-step with copy-paste commands.

DEPLOY NOW

Verify & Test

URLs to check, expected behavior, troubleshooting common issues.

SEO — SHOPS

SSR / Pre-Rendering

shops.beargrips.com needs server-side rendering for Google to index it. Options and verification steps.

SEO — SHOPS

Sitemap & Robots.txt

XML sitemap with all public pages + robots.txt to block private routes.

SEO — SHOPS

Meta Tags & Canonicals

Unique title, description, OG, and Twitter card tags per page. Canonical URL setup.

SEO — SHOPS

Schema Markup

JSON-LD structured data for Organization, Product, FAQ. Copy-paste into <head>.

SEO — SHOPS

Page Speed & SSL

Target 90+ PageSpeed score. Image compression, lazy loading, caching, HTTPS redirect.

SEO — SHOPS

Google Search Console

Verification, sitemap submission, and monitoring setup for both domains.

SEO — SHOPS

Favicon & Web Manifest

Browser tab icon, Apple/Android home screen icons, web manifest. Files provided — just add to site.

SEO — SHOPS

Vendor Shop SEO (High Priority)

Pre-rendered body content, Product schema, dynamic sitemap for vendor shops. Every vendor shop = free SEO pages.

DEPLOY NOW

Free Tools Pages

Deploy 7 free design tools + hub page to shops.beargrips.com/free-design-tools/. Upload, verify, add to sitemap.

SEO — TRAFFIC

Niche Landing Pages (117)

117 niche-specific landing pages (gym, church, sports, trades, etc.) targeting long-tail SEO keywords. Same tools, niche copy.

DEPLOY NOW

Product Catalog (85 Pages)

85 SEO pages: 1 hub + 11 category + 7 brand + 3 audience + 63 product pages. Competes with Apliiq/Printify. Images + sitemap included.

CRITICAL

Security Hardening

Stripe protection, auth security, server headers, DDoS protection, input validation, content scraping prevention. Must-do before launch.

LIVE NOW

pSEO Pages (2,100+)

2,100+ programmatic SEO pages already deployed on Cloudflare Pages. Resources, comparisons, glossary, how-to guides, product pages, and more.

Priority Order

#TaskSitePriority
1Deploy Directory App (upload, build, nginx, SSL)directory.beargrips.comDO NOW
2Verify directory is live and all pages workdirectory.beargrips.comDO NOW
3SSR / Pre-Rendering for shopsshops.beargrips.comHIGH
4Sitemap + Robots.txt for shopsshops.beargrips.comHIGH
5Meta tags + canonical URLsshops.beargrips.comHIGH
6Schema markupshops.beargrips.comMEDIUM
7Page speed optimizationshops.beargrips.comMEDIUM
8Google Search Console for both domainsBothMEDIUM
9Vendor shop SEO (body content, schema, sitemap)shops.beargrips.comHIGH
10Favicon & web manifestshops.beargrips.comLOW
11Deploy Free Tools pagesshops.beargrips.comDO NOW
12Deploy 117 Niche Landing Pagesshops.beargrips.comDO NOW
13Deploy Product Catalog (85 SEO pages)shops.beargrips.comDO NOW
14Security Hardening (Stripe, auth, headers, DDoS)shops.beargrips.comCRITICAL

Deploy Directory App

Deploy the gym directory at directory.beargrips.com on the existing Digital Ocean server.

DNS is already done

KJ added an A record on GoDaddy: directory167.172.23.233. You just need to set up the app on the server.

What is this app?

Next.js 16 gym directory — 104,000+ gyms across 50 states. Pre-built static pages (SSG). No database, no .env, no external services. Runs alongside shops on the same server, different port.

Server Details

ItemValue
Server IP167.172.23.233
Domaindirectory.beargrips.com
App FrameworkNext.js 16.1.6
Node.js Required20+
Port3001 (or any open port — don't conflict with shops)
DatabaseNone
.env FileNone needed
Suggested Path/var/www/directory-beargrips/

1 Upload the Project Files

Copy the entire project folder to the server. You can SCP, rsync, or clone from git. Suggested location:

/var/www/directory-beargrips/

Important: The public/data/ folder contains the data files (~100MB). Make sure these are included in the upload.

2 Install & Build

SSH into the server and run:

cd /var/www/directory-beargrips npm install --production npm run build

Build takes 10-30 minutes

The app pre-generates 16,000+ static pages at build time. This is expected. Don't kill the process — let it finish. The staticPageGenerationTimeout is set to 600 seconds in the config.

3 Start with PM2

Start the app on port 3001 using PM2:

pm2 start npm --name "directory" -- start -- -p 3001

If PM2 is not installed:

npm install -g pm2

Verify it's running:

curl http://localhost:3001

You should see HTML output. If you see an error, check pm2 logs directory.

Save PM2 config so it restarts on reboot:

pm2 save pm2 startup

4 Nginx Server Block

Create the nginx config file:

sudo nano /etc/nginx/sites-available/directory.beargrips.com

Paste this entire config:

server { listen 80; server_name directory.beargrips.com; location / { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_cache_bypass $http_upgrade; } }

Enable the site and reload nginx:

sudo ln -s /etc/nginx/sites-available/directory.beargrips.com /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx

Note: nginx -t tests the config. If it says "test is successful", you're good. If it shows an error, check for typos in the config file.

5 SSL Certificate

Use Let's Encrypt (certbot) to add HTTPS:

sudo certbot --nginx -d directory.beargrips.com

Certbot will automatically modify the nginx config to handle SSL. It also sets up auto-renewal.

If certbot is not installed:

sudo apt install certbot python3-certbot-nginx

Updating the App Later

When KJ sends you updated files:

cd /var/www/directory-beargrips # Upload new files (git pull, rsync, scp, etc.) npm run build pm2 restart directory

Verify & Test

After deploying, verify these URLs work correctly.

URLs to Test

URLExpected
https://directory.beargrips.comHomepage with state grid, search bar, stats
https://directory.beargrips.com/gyms/flFlorida state page with cities grid
https://directory.beargrips.com/gyms/fl/miamiMiami city page with gym cards + map
https://directory.beargrips.com/gyms/ca/los-angelesLA city page (large city, many gyms)
https://directory.beargrips.com/api/search?q=miamiJSON search results

Checklist

Troubleshooting

"502 Bad Gateway"

The app isn't running. Check:

pm2 status pm2 logs directory

"This site can't be reached" / DNS error

DNS hasn't propagated yet. Wait 30-60 minutes after the A record was added. Check propagation:

dig directory.beargrips.com

Should return 167.172.23.233.

Build fails / Out of memory

The build generates 16,000+ pages and needs RAM. If the server has limited memory:

# Increase Node memory limit NODE_OPTIONS="--max-old-space-size=4096" npm run build

Port conflict

If port 3001 is in use, pick another port. Update both the PM2 start command and the nginx proxy_pass line to match.

SSR / Pre-Rendering for shops.beargrips.com

Priority: CRITICAL — If Google can't render the JavaScript, none of our SEO work matters.

Why this matters

shops.beargrips.com is JavaScript-rendered. Google's crawler may see a blank page if it can't execute JS fast enough. A client with 500 CSR pages had only 3 indexed by Google. We need server-rendered HTML.

Solution Options (pick one)

Option A: Pre-Rendering Service (Fastest to implement)

Option B: Server-Side Rendering (SSR)

Option C: Static Site Generation (SSG)

How to verify it works

curl -A "Googlebot" https://shops.beargrips.com

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

Sitemap.xml

Create at: https://shops.beargrips.com/sitemap.xml

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://shops.beargrips.com/</loc> <changefreq>weekly</changefreq> <priority>1.0</priority> </url> <url> <loc>https://shops.beargrips.com/how-it-works</loc> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> <url> <loc>https://shops.beargrips.com/catalog</loc> <changefreq>weekly</changefreq> <priority>0.8</priority> </url> <url> <loc>https://shops.beargrips.com/pricing</loc> <changefreq>monthly</changefreq> <priority>0.9</priority> </url> <url> <loc>https://shops.beargrips.com/features</loc> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> <url> <loc>https://shops.beargrips.com/help</loc> <changefreq>monthly</changefreq> <priority>0.5</priority> </url> <url> <loc>https://shops.beargrips.com/resources</loc> <changefreq>monthly</changefreq> <priority>0.5</priority> </url> <url> <loc>https://shops.beargrips.com/terms</loc> <changefreq>yearly</changefreq> <priority>0.3</priority> </url> <url> <loc>https://shops.beargrips.com/privacy</loc> <changefreq>yearly</changefreq> <priority>0.3</priority> </url> <url> <loc>https://shops.beargrips.com/refund-policy</loc> <changefreq>yearly</changefreq> <priority>0.3</priority> </url> <url> <loc>https://shops.beargrips.com/cancellation-policy</loc> <changefreq>yearly</changefreq> <priority>0.3</priority> </url> </urlset>

Notes:

Robots.txt

Ready-made file provided

KJ will send you robots.txt — just drop it in the public/root directory. It blocks private routes and references both sitemaps.

Place at: https://shops.beargrips.com/robots.txt

Preview the robots.txt file →

Meta Tags & Canonical URLs

Canonical URLs

Add to the <head> of every public page:

<link rel="canonical" href="https://shops.beargrips.com/CURRENT-PAGE-PATH" />

This should be dynamic — pull the current page path and build the URL. Prevents Google from treating URL variations as separate pages.

Meta Tags Infrastructure

Each public page needs these tags in <head>. KJ will provide the specific copy per page — dev just needs to wire up the infrastructure so each page can have unique values.

<!-- Page-specific (unique per page) --> <title>Page Title Here | Bear Grips Pro Shops</title> <meta name="description" content="Page description here..." /> <meta name="robots" content="index, follow" /> <!-- Open Graph (social sharing) --> <meta property="og:title" content="Page Title" /> <meta property="og:description" content="Page description" /> <meta property="og:image" content="https://shops.beargrips.com/og-image.jpg" /> <meta property="og:url" content="https://shops.beargrips.com/current-path" /> <meta property="og:type" content="website" /> <meta property="og:site_name" content="Bear Grips Pro Shops" /> <!-- Twitter Card --> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content="Page Title" /> <meta name="twitter:description" content="Page description" /> <meta name="twitter:image" content="https://shops.beargrips.com/og-image.jpg" />

Framework Implementation

Schema Markup

Add this JSON-LD to the <head> of the shops.beargrips.com homepage. This tells Google what the business is.

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "Bear Grips Pro Shops", "applicationCategory": "BusinessApplication", "operatingSystem": "Web", "description": "Print-on-demand custom apparel platform for gyms and fitness businesses. Get a free branded merch shop with no inventory, no minimums, and free USA shipping.", "url": "https://shops.beargrips.com", "offers": [ { "@type": "Offer", "name": "Free Plan", "price": "0", "priceCurrency": "USD", "description": "3 live products, full catalog access, higher base prices" }, { "@type": "Offer", "name": "Self-Service VIP", "price": "59", "priceCurrency": "USD", "description": "200 live products, lowest base prices, full control" }, { "@type": "Offer", "name": "Done-For-You VIP", "price": "109", "priceCurrency": "USD", "description": "250 live products, 1 design/month set up on top 15 best-sellers + 5 vendor's choice" } ], "provider": { "@type": "Organization", "name": "Bear Grips", "url": "https://www.beargrips.com", "logo": "https://www.beargrips.com/cdn/shop/files/Bear_Grips_Logo_on_transparent_400x.png" } } </script>

Notes:

Page Speed & SSL

Page Speed — Target 90+

Test at: pagespeed.web.dev

Common Fixes

SSL / HTTPS

Mobile Responsiveness

Google Search Console

We need GSC set up for both domains so we can monitor indexing and search performance.

For shops.beargrips.com

  1. Go to search.google.com/search-console
  2. Click "Add Property"
  3. Enter: https://shops.beargrips.com
  4. Choose verification method:
    • DNS method (recommended): Add a TXT record to DNS for beargrips.com
    • Meta tag method: Add a <meta> tag to homepage <head>
    • HTML file method: Upload a verification file to root
  5. Once verified → Sitemaps → Submit https://shops.beargrips.com/sitemap.xml

STEP 1 — Add this meta tag to shops.beargrips.com (DO THIS FIRST)

Add this to the <head> of the shops.beargrips.com homepage. Do NOT remove it after verification — it must stay permanently.

<meta name="google-site-verification" content="ixzjWs_TY1tAtRtN00_K4YBug8Z7uPOJQQCclLfIIFI" />

After adding, tell KJ so he can click "Verify" in Google Search Console.

For directory.beargrips.com

  1. Same process — KJ will add as separate property and provide the verification meta tag
  2. Enter: https://directory.beargrips.com
  3. Submit sitemap: https://directory.beargrips.com/sitemap.xml
  4. The directory app auto-generates its sitemap — it should be available at that URL once deployed

What to monitor after launch

Check GSC weekly for: pages indexed vs discovered, crawl errors, mobile usability issues, and search queries driving traffic. The directory should start showing indexed pages within 1-2 weeks of sitemap submission.

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.

Why this is high priority

When a gym owner publishes products, those pages should rank for "[Gym Name] + apparel/tee/hoodie" in Google. This worked automatically on Shopify. On our custom platform, we need to make sure Google can see the full page content — not just meta tags.

What's Working (Good job!)

Issue 0: Hide Printify Image URLs (Security)

Competitive risk — Printify URLs are exposed

Product mockup images currently point directly to images-api.printify.com. Anyone viewing page source can see we use Printify, and can find the exact blueprint/variant IDs. We need to proxy these through our own domain.

Current (exposed):

<meta property="og:image" content="https://images-api.printify.com/mockup/69abb695.../comfort-soft-hoodie.jpg?camera_label=front">

What it should look like:

<meta property="og:image" content="https://shops.beargrips.com/product-images/69abb695.../comfort-soft-hoodie.jpg">

Fix: Nginx reverse proxy (quickest approach)

Add this to the nginx config for shops.beargrips.com:

location /product-images/ { proxy_pass https://images-api.printify.com/mockup/; proxy_set_header Host images-api.printify.com; proxy_set_header Accept-Encoding ""; proxy_ssl_server_name on; proxy_cache_valid 200 7d; proxy_cache_use_stale error timeout updating; expires 7d; add_header Cache-Control "public, max-age=604800"; add_header X-Content-Type-Options "nosniff"; }

Then update the app code to rewrite all Printify mockup URLs:

Also check for leaks in:

Issue 1: Empty Body Content

The <body> is empty for crawlers

Currently the pre-rendered HTML only has meta tags in <head> and a noscript redirect in <body>. Google weighs body content much heavier than meta tags. We need to render the actual product info in the body.

Current product page body (what Google sees):

<body> <noscript> <p>If you are not redirected automatically, <a href="...">click here</a>.</p> </noscript> </body>

What it should look like (use the same data already in the meta tags):

<body> <h1>Women's Favorite Tee - Bella+Canvas</h1> <p>By <a href="/proshops/gallerybarre">Gallery Barre</a></p> <p>$29.99</p> <img src="https://images-api.printify.com/mockup/.../front.jpg" alt="Women's Favorite Tee - Bella+Canvas - Gallery Barre" /> <p>Elevate your wardrobe with a versatile essential designed for both style and performance...</p> <noscript> <p>If you are not redirected automatically, <a href="...">click here</a>.</p> </noscript> <script>window.location.href = "...";</script> </body>

Same approach for shop pages:

<body> <h1>Gallery Barre</h1> <p>Shop custom apparel from Gallery Barre.</p> <img src="[vendor logo url]" alt="Gallery Barre logo" /> <!-- List published products --> <ul> <li><a href="/proshops/gallerybarre/product/abc123">Women's Favorite Tee - $29.99</a></li> <li><a href="/proshops/gallerybarre/product/def456">Performance Tank - $24.99</a></li> </ul> <noscript>...</noscript> <script>window.location.href = "...";</script> </body>

Key points:

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):

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Product", "name": "Women's Favorite Tee - Bella+Canvas", "description": "Elevate your wardrobe with a versatile essential...", "image": "https://images-api.printify.com/mockup/.../front.jpg", "brand": { "@type": "Brand", "name": "Gallery Barre" }, "offers": { "@type": "Offer", "url": "https://shops.beargrips.com/proshops/gallerybarre/product/699f8abd...", "priceCurrency": "USD", "price": "29.99", "availability": "https://schema.org/InStock", "seller": { "@type": "Organization", "name": "Gallery Barre" } } } </script>

Dynamic fields to populate from existing data:

Schema FieldSource
nameProduct title (same as og:title minus store name)
descriptionProduct description (same as meta description)
imageProduct mockup URL (same as og:image)
brand.nameVendor store name
priceRetail price (same as product:price:amount)
urlFull product page URL
availabilityAlways "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:

<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <!-- Each vendor shop page --> <url> <loc>https://shops.beargrips.com/proshops/gallerybarre</loc> <changefreq>weekly</changefreq> <priority>0.7</priority> </url> <!-- Each published product --> <url> <loc>https://shops.beargrips.com/proshops/gallerybarre/product/699f8abd...</loc> <changefreq>weekly</changefreq> <priority>0.6</priority> </url> <!-- ... repeat for all vendors and products ... --> </urlset>

Implementation notes:

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:

Shop at [Store Name]. Browse our store.

Better default:

Shop custom apparel from [Store Name]. Browse premium tees, tanks, hoodies, joggers, hats and more. All items printed in the USA with free shipping. New designs added regularly.

Default Shop Title

Current:

[Store Name]

Better:

[Store Name] | Custom Apparel Shop

Default Product Page Title

Current format is good: [Product Name] - [Store Name]

Consider appending a tail keyword:

[Product Name] - [Store Name] | Custom Printed Apparel

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:

[Existing product description from vendor or system default] Available exclusively from [Store Name]. Printed on demand in the USA. Free shipping on every order.

Why this helps vendors:

Price Display

Minor fix: Round prices to 2 decimal places in all meta tags. Currently showing raw values like 43.5552 and 26.9152.

// Before product:price:amount = 43.5552 // After product:price:amount = 43.56

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:

<img src="/product-images/69abb695.../comfort-soft-hoodie.jpg">

Fix — add descriptive alt text:

<img src="/product-images/69abb695.../comfort-soft-hoodie.jpg" alt="Comfort Soft Hoodie - Bitcoin Apparel">

Pattern: alt="[Product Name] - [Store Name]"

Apply to:

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:

<!-- Shop page --> <link rel="canonical" href="https://shops.beargrips.com/proshops/bitcoin-apparel" /> <!-- Product page --> <link rel="canonical" href="https://shops.beargrips.com/proshops/bitcoin-apparel/product/699cc3cc90545a0c8aa80aeb" />

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:

<meta name="robots" content="noindex, nofollow" />

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:

<meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="1200" />

If the actual mockup dimensions differ, use those values instead. Square (1200x1200) or landscape (1200x630) are both fine.

Issue 10: Custom 404 Page

Ready-made file provided

KJ will send you 404.html — a complete, styled 404 page with Bear Grips branding, homepage/catalog links, and a "Open Your Free Shop" CTA. Just wire it up as the 404 route.

To implement:

Preview the 404 page →

Impact

Once these fixes are in place, every vendor shop becomes a free SEO asset:

Favicon & Web Manifest

Favicon files are ready — just add them to shops.beargrips.com. Google shows favicons in search results now, so this matters.

Directory app already has these

The favicons are already wired into directory.beargrips.com. This section is for adding them to shops.beargrips.com.

Files to Add

KJ will provide these files (already generated). Place them in the root/public directory of the shops app:

FileSizePurpose
favicon.ico16+32+48pxBrowser tab (legacy browsers)
favicon-16x16.png16x16Browser tab
favicon-32x32.png32x32Browser tab (retina)
apple-touch-icon.png180x180iPhone home screen bookmark
android-chrome-192x192.png192x192Android home screen
android-chrome-512x512.png512x512Android splash screen

HTML Tags

Add these to the <head> of every page:

<link rel="icon" href="/favicon.ico" sizes="48x48"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="manifest" href="/site.webmanifest"> <meta name="theme-color" content="#E31837">

Web Manifest File

Create site.webmanifest in the public/root directory:

{ "name": "Bear Grips Pro Shops", "short_name": "Bear Grips", "icons": [ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "theme_color": "#E31837", "background_color": "#ffffff", "display": "standalone" }

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.

What are these?

7 free browser-based design tools (background remover, image cropper, file compressor, transparent PNG checker, image-to-PNG converter, image resizer, text design generator) plus a hub page. They drive organic SEO traffic and funnel visitors to sign up for a Pro Shop. All processing happens client-side — no server-side dependencies.

Files to Deploy

#FileURL on Live Site
1index.html/free-design-tools/
2background-remover.html/free-design-tools/background-remover.html
3image-cropper.html/free-design-tools/image-cropper.html
4file-compressor.html/free-design-tools/file-compressor.html
5transparent-png-checker.html/free-design-tools/transparent-png-checker.html
6image-to-png-converter.html/free-design-tools/image-to-png-converter.html
7image-resizer.html/free-design-tools/image-resizer.html
8text-design-generator.html/free-design-tools/text-design-generator.html
9assets/ folderLogo, 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:

sudo mkdir -p /var/www/shops-beargrips/free-design-tools sudo mkdir -p /var/www/shops-beargrips/free-design-tools/assets

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:

# Upload all HTML files + assets scp index.html background-remover.html image-cropper.html file-compressor.html transparent-png-checker.html image-to-png-converter.html image-resizer.html text-design-generator.html user@167.172.23.233:/var/www/shops-beargrips/free-design-tools/ # Upload assets folder (logo + favicons) scp -r assets/ user@167.172.23.233:/var/www/shops-beargrips/free-design-tools/assets/

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):

<url> <loc>https://shops.beargrips.com/free-design-tools/</loc> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/background-remover.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/image-cropper.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/file-compressor.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/transparent-png-checker.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/image-to-png-converter.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/image-resizer.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url> <url> <loc>https://shops.beargrips.com/free-design-tools/text-design-generator.html</loc> <changefreq>monthly</changefreq> <priority>0.7</priority> </url>

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"

Nginx note

If the shops app uses nginx to proxy all requests to the Node app, you may need to add a location /free-design-tools/ block to serve these as static files directly, before the proxy_pass catch-all. Ask KJ if unsure.

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."

Why niche pages?

Each page targets a unique long-tail keyword (e.g. "free design tools for gym apparel"). 117 pages = 117 chances to rank in Google. Same tools, different copy — zero extra server cost. All pages funnel to shops.beargrips.com signup.

Directory Structure

All niche pages live inside a niche/ subfolder within the free-design-tools directory:

/free-design-tools/ index.html ← Main tools hub background-remover.html ← 7 tool pages image-cropper.html file-compressor.html transparent-png-checker.html image-to-png-converter.html image-resizer.html text-design-generator.html assets/ ← Logo + favicons niche/ ← All 45+ niche landing pages gym-apparel.html crossfit-apparel.html church-apparel.html ... (45+ files)

All 117 Niche Pages to Upload

#FileLive URL
1after-school-program-apparel.html/free-design-tools/niche/after-school-program-apparel
2auto-shop-apparel.html/free-design-tools/niche/auto-shop-apparel
3bachelorette-party-shirts.html/free-design-tools/niche/bachelorette-party-shirts
4bachelor-party-shirts.html/free-design-tools/niche/bachelor-party-shirts
5bakery-apparel.html/free-design-tools/niche/bakery-apparel
6bar-apparel.html/free-design-tools/niche/bar-apparel
7barbershop-apparel.html/free-design-tools/niche/barbershop-apparel
8barre-studio-apparel.html/free-design-tools/niche/barre-studio-apparel
9baseball-apparel.html/free-design-tools/niche/baseball-apparel
10basketball-apparel.html/free-design-tools/niche/basketball-apparel
11birthday-shirts.html/free-design-tools/niche/birthday-shirts
12boat-club-apparel.html/free-design-tools/niche/boat-club-apparel
13bootcamp-apparel.html/free-design-tools/niche/bootcamp-apparel
14boxing-gym-apparel.html/free-design-tools/niche/boxing-gym-apparel
15boy-scouts-apparel.html/free-design-tools/niche/boy-scouts-apparel
16brewery-merch.html/free-design-tools/niche/brewery-merch
17cafe-apparel.html/free-design-tools/niche/cafe-apparel
18car-club-apparel.html/free-design-tools/niche/car-club-apparel
19catering-apparel.html/free-design-tools/niche/catering-apparel
20cheerleading-apparel.html/free-design-tools/niche/cheerleading-apparel
21church-apparel.html/free-design-tools/niche/church-apparel
22college-apparel.html/free-design-tools/niche/college-apparel
23company-swag.html/free-design-tools/niche/company-swag
24conference-apparel.html/free-design-tools/niche/conference-apparel
25construction-apparel.html/free-design-tools/niche/construction-apparel
26country-club-apparel.html/free-design-tools/niche/country-club-apparel
27creator-merch.html/free-design-tools/niche/creator-merch
28cruise-apparel.html/free-design-tools/niche/cruise-apparel
29dance-studio-apparel.html/free-design-tools/niche/dance-studio-apparel
30daycare-apparel.html/free-design-tools/niche/daycare-apparel
31dentist-apparel.html/free-design-tools/niche/dentist-apparel
32dispensary-apparel.html/free-design-tools/niche/dispensary-apparel
33doctor-office-apparel.html/free-design-tools/niche/doctor-office-apparel
34dog-trainer-apparel.html/free-design-tools/niche/dog-trainer-apparel
35dog-walker-apparel.html/free-design-tools/niche/dog-walker-apparel
36dropshipping-apparel.html/free-design-tools/niche/dropshipping-apparel
37electrician-apparel.html/free-design-tools/niche/electrician-apparel
38elementary-school-apparel.html/free-design-tools/niche/elementary-school-apparel
39entrepreneur-apparel.html/free-design-tools/niche/entrepreneur-apparel
40event-planner-apparel.html/free-design-tools/niche/event-planner-apparel
41family-reunion-shirts.html/free-design-tools/niche/family-reunion-shirts
42farmers-market-apparel.html/free-design-tools/niche/farmers-market-apparel
43fire-department-apparel.html/free-design-tools/niche/fire-department-apparel
44fishing-apparel.html/free-design-tools/niche/fishing-apparel
45flag-football-apparel.html/free-design-tools/niche/flag-football-apparel
46food-truck-apparel.html/free-design-tools/niche/food-truck-apparel
47fraternity-sorority-apparel.html/free-design-tools/niche/fraternity-sorority-apparel
48functional-fitness-apparel.html/free-design-tools/niche/functional-fitness-apparel
49girl-scouts-apparel.html/free-design-tools/niche/girl-scouts-apparel
50golf-apparel.html/free-design-tools/niche/golf-apparel
51graduation-apparel.html/free-design-tools/niche/graduation-apparel
52gym-apparel.html/free-design-tools/niche/gym-apparel
53gymnastics-apparel.html/free-design-tools/niche/gymnastics-apparel
54high-school-apparel.html/free-design-tools/niche/high-school-apparel
55hiit-gym-apparel.html/free-design-tools/niche/hiit-gym-apparel
56hiking-club-apparel.html/free-design-tools/niche/hiking-club-apparel
57hockey-apparel.html/free-design-tools/niche/hockey-apparel
58hospital-apparel.html/free-design-tools/niche/hospital-apparel
59hvac-apparel.html/free-design-tools/niche/hvac-apparel
60hybrid-athlete-apparel.html/free-design-tools/niche/hybrid-athlete-apparel
61instagram-merch.html/free-design-tools/niche/instagram-merch
62insurance-apparel.html/free-design-tools/niche/insurance-apparel
63kickboxing-apparel.html/free-design-tools/niche/kickboxing-apparel
64lacrosse-apparel.html/free-design-tools/niche/lacrosse-apparel
65landscaping-apparel.html/free-design-tools/niche/landscaping-apparel
66law-firm-apparel.html/free-design-tools/niche/law-firm-apparel
67martial-arts-apparel.html/free-design-tools/niche/martial-arts-apparel
68micro-influencer-merch.html/free-design-tools/niche/micro-influencer-merch
69middle-school-apparel.html/free-design-tools/niche/middle-school-apparel
70mma-apparel.html/free-design-tools/niche/mma-apparel
71mortgage-apparel.html/free-design-tools/niche/mortgage-apparel
72nonprofit-fundraiser.html/free-design-tools/niche/nonprofit-fundraiser
73nurse-apparel.html/free-design-tools/niche/nurse-apparel
74personal-brand-merch.html/free-design-tools/niche/personal-brand-merch
75personal-trainer-apparel.html/free-design-tools/niche/personal-trainer-apparel
76pest-control-apparel.html/free-design-tools/niche/pest-control-apparel
77pet-business-apparel.html/free-design-tools/niche/pet-business-apparel
78pickleball-apparel.html/free-design-tools/niche/pickleball-apparel
79pilates-studio-apparel.html/free-design-tools/niche/pilates-studio-apparel
80pizza-shop-apparel.html/free-design-tools/niche/pizza-shop-apparel
81plumbing-apparel.html/free-design-tools/niche/plumbing-apparel
82print-on-demand-platform.html/free-design-tools/niche/print-on-demand-platform
83print-on-demand-side-hustle.html/free-design-tools/niche/print-on-demand-side-hustle
84print-on-demand-tools-2026.html/free-design-tools/niche/print-on-demand-tools-2026
85private-school-apparel.html/free-design-tools/niche/private-school-apparel
86real-estate-apparel.html/free-design-tools/niche/real-estate-apparel
87rec-league-apparel.html/free-design-tools/niche/rec-league-apparel
88restaurant-apparel.html/free-design-tools/niche/restaurant-apparel
89rock-climbing-apparel.html/free-design-tools/niche/rock-climbing-apparel
90roofing-apparel.html/free-design-tools/niche/roofing-apparel
91running-club-apparel.html/free-design-tools/niche/running-club-apparel
92sales-team-apparel.html/free-design-tools/niche/sales-team-apparel
93salon-apparel.html/free-design-tools/niche/salon-apparel
94school-spirit-wear.html/free-design-tools/niche/school-spirit-wear
95side-hustle-apparel.html/free-design-tools/niche/side-hustle-apparel
96smoke-shop-apparel.html/free-design-tools/niche/smoke-shop-apparel
97soccer-apparel.html/free-design-tools/niche/soccer-apparel
98softball-apparel.html/free-design-tools/niche/softball-apparel
99spa-apparel.html/free-design-tools/niche/spa-apparel
100spin-studio-apparel.html/free-design-tools/niche/spin-studio-apparel
101startup-swag.html/free-design-tools/niche/startup-swag
102summer-camp-apparel.html/free-design-tools/niche/summer-camp-apparel
103swim-team-apparel.html/free-design-tools/niche/swim-team-apparel
104teacher-apparel.html/free-design-tools/niche/teacher-apparel
105tech-company-apparel.html/free-design-tools/niche/tech-company-apparel
106tennis-apparel.html/free-design-tools/niche/tennis-apparel
107tiktok-merch.html/free-design-tools/niche/tiktok-merch
108track-field-apparel.html/free-design-tools/niche/track-field-apparel
109university-apparel.html/free-design-tools/niche/university-apparel
110veterinary-apparel.html/free-design-tools/niche/veterinary-apparel
111volleyball-apparel.html/free-design-tools/niche/volleyball-apparel
112winery-apparel.html/free-design-tools/niche/winery-apparel
113wrestling-apparel.html/free-design-tools/niche/wrestling-apparel
114yoga-studio-apparel.html/free-design-tools/niche/yoga-studio-apparel
115youth-group-apparel.html/free-design-tools/niche/youth-group-apparel
116youth-sports-apparel.html/free-design-tools/niche/youth-sports-apparel
117youtuber-merch.html/free-design-tools/niche/youtuber-merch

Steps

1 Create the niche/ directory

Create a niche/ subfolder inside the free-design-tools directory:

sudo mkdir -p /var/www/shops-beargrips/free-design-tools/niche

2 Upload all 117 niche HTML files

Upload all .html files from the niche folder:

scp niche/*.html user@167.172.23.233:/var/www/shops-beargrips/free-design-tools/niche/

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.html
  • https://shops.beargrips.com/free-design-tools/niche/church-apparel.html
  • https://shops.beargrips.com/free-design-tools/niche/pickleball-apparel.html
  • Logo + favicon loads on every page (from ../assets/)
  • Tool links point to ../background-remover.html etc. (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:

<!-- Niche Landing Pages --> <url> <loc>https://shops.beargrips.com/free-design-tools/niche/gym-apparel.html</loc> <changefreq>monthly</changefreq> <priority>0.6</priority> </url> <!-- Repeat for all 117 niche pages -->

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:

# Add this BEFORE the proxy_pass catch-all block location /free-design-tools/ { alias /var/www/shops-beargrips/free-design-tools/; try_files $uri $uri/ =404; }

This one block covers both the main tools pages AND the niche subdirectory.

All 117 niche pages ready

All 117 niche pages are built and ready to upload. Just upload all .html files from the niche/ folder. No code changes needed. If more pages are added later, same process — drop new .html files into the niche/ folder.

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.

All 85 pages + images are built and ready

Generator script: built-pages/catalog/generate.js. Output: built-pages/catalog/. Run node built-pages/catalog/generate.js to regenerate if product data changes.

What's Included

TypeCountExample URL
Catalog Hub1/products/index.html
Category Pages11/products/custom-t-shirts.html, /products/custom-hoodies.html
Brand Pages7/products/brands/bella-canvas.html, /products/brands/champion.html
Audience Pages3/products/womens.html, /products/mens.html, /products/youth.html
Product Pages63/products/products/airlume-cotton-athletic-tee.html
XML Sitemap1/products/sitemap-products.xml
Product Images85/products/images/*.png (Bear Grips proprietary)
Brand Logos6/products/images/brands/*.png

SEO Features (Every Page)

Deployment Steps

1 Create /products/ directory on server

Create the products directory structure on the DO server:

sudo mkdir -p /var/www/shops-beargrips/products/images/brands sudo mkdir -p /var/www/shops-beargrips/products/products sudo mkdir -p /var/www/shops-beargrips/products/brands sudo mkdir -p /var/www/shops-beargrips/products/assets

2 Upload all files from built-pages/catalog/

Upload the entire catalog build output to the server:

# Upload root HTML files (hub + category + audience pages + sitemap) scp built-pages/catalog/*.html built-pages/catalog/*.xml user@167.172.23.233:/var/www/shops-beargrips/products/ # Upload product pages scp built-pages/catalog/products/*.html user@167.172.23.233:/var/www/shops-beargrips/products/products/ # Upload brand pages scp built-pages/catalog/brands/*.html user@167.172.23.233:/var/www/shops-beargrips/products/brands/ # Upload product images (~85 images) scp built-pages/catalog/images/*.png user@167.172.23.233:/var/www/shops-beargrips/products/images/ # Upload brand logos scp built-pages/catalog/images/brands/*.png user@167.172.23.233:/var/www/shops-beargrips/products/images/brands/ # Upload assets (logo, favicons) scp built-pages/catalog/assets/* user@167.172.23.233:/var/www/shops-beargrips/products/assets/

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:

# Product Catalog — static HTML pages location /products/ { alias /var/www/shops-beargrips/products/; try_files $uri $uri/ =404; } # Note: If you already have the /free-design-tools/ block, add this # right next to it — both go BEFORE the proxy_pass catch-all

Then reload nginx:

sudo nginx -t && sudo systemctl reload nginx

4 Add product sitemap to robots.txt

Add the product catalog sitemap reference to robots.txt:

# Add this line to robots.txt (alongside existing sitemaps) Sitemap: https://shops.beargrips.com/products/sitemap-products.xml

5 Verify pages load correctly

Test these URLs after deployment:

  • https://shops.beargrips.com/products/ — Catalog hub with all categories, brands, featured products
  • https://shops.beargrips.com/products/custom-t-shirts.html — Category page
  • https://shops.beargrips.com/products/brands/bella-canvas.html — Brand page
  • https://shops.beargrips.com/products/womens.html — Audience page
  • https://shops.beargrips.com/products/products/airlume-cotton-athletic-tee.html — Product page
  • https://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:

https://shops.beargrips.com/products/

This passes link equity from the homepage to all 85 catalog pages.

Regenerating pages (if products change)

If product data is updated in docs/products.json, regenerate all pages by running: node built-pages/catalog/generate.js — then re-upload the output. The script generates all 85 pages + sitemap in ~2 seconds.

File Structure on Server

/var/www/shops-beargrips/products/ ├── index.html # Catalog hub ├── custom-t-shirts.html # Category pages (11) ├── custom-hoodies.html ├── custom-tank-tops.html ├── custom-shorts.html ├── custom-hats.html ├── custom-leggings.html ├── custom-joggers.html ├── custom-polos.html ├── custom-quarter-zips.html ├── custom-sweatpants.html ├── custom-long-sleeves.html ├── womens.html # Audience pages (3) ├── mens.html ├── youth.html ├── sitemap-products.xml # XML sitemap (85 URLs) ├── assets/ # Logo + favicons │ ├── logo-white.png │ └── favicon files... ├── images/ # Product images (85) │ ├── airlume-cotton-athletic-tee.png │ ├── ... (63 product images) │ └── brands/ # Brand logos (6) │ ├── bear-grips-logo.png │ ├── bella-canvas-logo.png │ ├── champion-logo.png │ ├── gildan-logo.png │ ├── next-level-logo.png │ └── sport-tek-logo.png ├── brands/ # Brand pages (7) │ ├── bear-grips.html │ ├── bella-canvas.html │ ├── champion.html │ ├── gildan.html │ ├── next-level.html │ ├── sport-tek.html │ └── yupoong.html └── products/ # Individual product pages (63) ├── airlume-cotton-athletic-tee.html ├── womens-favorite-tee.html └── ... (63 total)

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.

Live URL: bear-grips-pseo.pages.dev

2,100+ static HTML pages deployed on Cloudflare Pages (free tier, unlimited bandwidth). Pages are SEO-optimized with JSON-LD schema, OG tags, mobile-responsive, and cross-linked.

Page Types & Counts

PhaseTypePagesExample URL
1Resource Pages (ideas, checklists, guides)354/resources/custom-gym-apparel-ideas.html
2Competitor Comparisons35/comparisons/bear-grips-vs-printful.html
3POD Glossary160/glossary/print-on-demand.html
4Profit Calculator1/tools/profit-calculator.html
5Affiliate / Side Hustle8/affiliate/best-pod-side-hustles-2026.html
6Occasion / Seasonal17/occasions/fathers-day-custom-apparel.html
7Vendor Spotlight Gallery8/vendors/gallery.html
8Fundraising Content11/fundraising/church-fundraiser-apparel.html
9Deals / Coupons1/deals/
10Niche Deep-Dives (sub-niches)620/tools/niche/boxing-gym-apparel.html
11Product × Niche821/product-niche/hoodies-for-yoga-studio-apparel.html
12How-To Guides118/how-to/how-to-start-a-gym-apparel-merch-business.html
TOTAL2,163+

What These Pages Do

Hosting Details

Future: Custom Domain

These pages currently live on bear-grips-pseo.pages.dev. When ready, they can be moved to a custom subdomain (e.g., content.beargrips.com) or integrated into shops.beargrips.com via reverse proxy / Cloudflare routing rules.

Security Hardening

Pre-launch security checklist for shops.beargrips.com. Stripe handles real money, vendor accounts hold business data — this is not optional.

CRITICAL — Complete Before Public Launch

These items protect vendor accounts, Stripe payments, and business data. A single breach can kill trust permanently. Work through each section in order.

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.

// In your webhook handler const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); } catch (err) { console.log(`Webhook signature verification failed.`, err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } // Only process verified events switch (event.type) { case 'checkout.session.completed': // Handle payment... break; case 'customer.subscription.updated': // Handle plan change... break; }

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:

// Cookie settings for session tokens { httpOnly: true, // JS can't read the cookie (prevents XSS token theft) secure: true, // Only sent over HTTPS sameSite: 'strict' // Not sent with cross-site requests (prevents CSRF) }

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.

# Add to nginx server block for shops.beargrips.com # Security Headers — REQUIRED before launch # Prevent clickjacking (embedding your site in iframes) add_header X-Frame-Options "DENY" always; # Block MIME-type sniffing (prevents image-as-script attacks) add_header X-Content-Type-Options "nosniff" always; # Enable browser XSS filter add_header X-XSS-Protection "1; mode=block" always; # Control referrer info sent to other sites add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Force HTTPS for 1 year (only add AFTER SSL is confirmed working) add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Disable camera, microphone, geolocation access add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; # Content Security Policy — allow only trusted sources # IMPORTANT: Adjust script-src and connect-src if you add new third-party services add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; frame-src https://js.stripe.com; connect-src 'self' https://api.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self';" always; # Disable directory listing autoindex off; # Force HTTPS redirect server { listen 80; server_name shops.beargrips.com; return 301 https://$host$request_uri; }

Test CSP Before Going Live

Content-Security-Policy can break things if too restrictive. Start with Content-Security-Policy-Report-Only to find issues without blocking, then switch to enforcing once verified.

4. DDoS & Bot Protection (Cloudflare)

Put Cloudflare in front of shops.beargrips.com. The free tier gives you instant DDoS protection, bot filtering, SSL, and CDN caching.

Setup Steps

  1. Create a free Cloudflare account at dash.cloudflare.com
  2. Add shops.beargrips.com → Cloudflare scans existing DNS records
  3. Update nameservers at GoDaddy to Cloudflare's (Cloudflare will tell you which ones)
  4. Enable "Under Attack Mode" if you ever see a spike in malicious traffic
  5. Turn on "Bot Fight Mode" in Security → Bots (free tier)
  6. Set SSL mode to "Full (strict)" since you already have Let's Encrypt certs

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.

// Simple in-memory rate limiter (same pattern as directory app) const rateLimiter = new Map(); const RATE_LIMIT = 30; // max requests const RATE_WINDOW = 60000; // per 60 seconds function checkRateLimit(ip) { const now = Date.now(); const record = rateLimiter.get(ip); if (!record || now - record.start > RATE_WINDOW) { rateLimiter.set(ip, { count: 1, start: now }); return true; } record.count++; return record.count <= RATE_LIMIT; } // Apply to Express/API routes app.use('/api/', (req, res, next) => { const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; if (!checkRateLimit(ip)) { return res.status(429).json({ error: 'Too many requests. Try again later.' }); } next(); }); // Stricter limit for login (5 per minute) app.use('/api/auth/login', (req, res, next) => { const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress; if (!checkRateLimit('login:' + ip, 5, 60000)) { return res.status(429).json({ error: 'Too many login attempts. Wait 1 minute.' }); } next(); });

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 dangerouslySetInnerHTML with user content
  • Sanitize any user-generated content displayed on public pages (shop names, product descriptions)
  • Use a library like DOMPurify if you must render HTML from user input

File Upload Security

If vendors upload logos or designs:

  • Validate file type server-side (check magic bytes, not just extension)
  • Enforce size limit: 5-10MB max
  • Rename uploaded files (never use the original filename)
  • Store uploads outside the web root or on a CDN (S3, Cloudflare R2)
  • Never execute uploaded files — serve with Content-Disposition: attachment or proper MIME types

7. Data Protection

Environment Variables

  • All API keys, DB credentials, Stripe keys, secrets in .env — never committed to git
  • Verify .gitignore includes: .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

# Add these to shops.beargrips.com/robots.txt User-agent: * Disallow: /api/ Disallow: /admin/ Disallow: /dashboard/ Disallow: /auth/ Disallow: /account/ Disallow: /vendor/settings/

8. Monitoring & Alerts

Error Logging

  • Sentry (free tier) — catches frontend + backend errors with stack traces. Install: npm install @sentry/node
  • Log all failed login attempts with IP and timestamp
  • Log all Stripe webhook failures
  • Never log sensitive data (passwords, card numbers, full API keys)

Uptime Monitoring

  • UptimeRobot (free) — monitors shops.beargrips.com every 5 minutes, alerts on downtime via email/SMS
  • Monitor both the homepage and at least one API endpoint
  • Set up status page so vendors know if there's an outage

Quick Verification Checklist

After implementing, run these checks:

#CheckHow to Test
1HTTPS forcedVisit http://shops.beargrips.com — should redirect to https://
2Security headersRun securityheaders.com — aim for A or A+ grade
3Stripe webhooks verifiedSend a test webhook from Stripe dashboard → confirm it's processed. Then send a fake POST to your webhook URL → confirm it's rejected
4Rate limiting worksHit /api/auth/login 6+ times rapidly → should get 429 on attempt 6
5XSS blockedTry entering <script>alert(1)</script> as a shop name → should be escaped/rejected
6No directory listingVisit a directory URL like /api/ directly → should get 404/403, not a file listing
7SSL cert validRun ssllabs.com/ssltest — aim for A grade
8Sensitive routes blockedVisit /admin/, /dashboard/ as logged-out user → should redirect to login, not show content