SOP: SEO + Search Indexing for Web Applications
SOP: SEO + Search Indexing for Web Applications
Section titled “SOP: SEO + Search Indexing for Web Applications”Document Type: Standard Operating Procedure (SOP) Version: 1.0 Status: Approved for Use Audience: Technician + Project Lead Confidentiality: Internal Platforms Supported: Any web application (React/Next.js/Astro/static)
1. Purpose
Section titled “1. Purpose”To ensure every client website is discoverable, indexable, and rich-result eligible in Google and Bing from day one. This SOP covers the technical schema work that must ship with the site, plus the post-launch submission process that triggers initial crawls.
Skipping these steps doesn’t break the site — but it can leave it functionally invisible for weeks while organic discovery slowly works. Following this SOP gets a new client to “appearing in search” within 24-72 hours instead.
2. Scope
Section titled “2. Scope”This SOP applies to:
- New website launches (all WTS site builds)
- Site re-platformings where URLs may have shifted
- Existing-site SEO audits / re-indexing after a major rewrite
- Any web application that should rank in organic search
Not included:
- Paid search / Google Ads campaigns
- Social media marketing (separate SOP)
- Off-site backlink strategy / outreach
- Content marketing / blog editorial calendars
3. Pre-Launch Build Requirements
Section titled “3. Pre-Launch Build Requirements”Everything in this section must be in place before submitting to Google. Submitting a half-baked site teaches Google to ignore you.
3.1. robots.txt
Section titled “3.1. robots.txt”Place at the public root (public/robots.txt or equivalent). Minimum content:
User-agent: *Allow: /
Sitemap: https://<client-domain>/sitemap.xmlAllow: /is explicit and overrides any default-deny edge cases- Sitemap URL is absolute — relative paths confuse crawlers
- No
Disallowdirectives unless deliberately blocking admin routes, share-token URLs, etc.
Common mistake: committing a Disallow: / line left over from a staging environment. Triple-check before launch.
3.2. sitemap.xml
Section titled “3.2. sitemap.xml”Place at the public root. Required elements per URL:
<url> <loc>https://<client-domain>/path</loc> <lastmod>YYYY-MM-DD</lastmod> <priority>0.7</priority></url>- Use canonical absolute URLs (HTTPS, no trailing slash unless the route truly serves at
/path/) lastmodis optional in spec but Google + Bing weight it. Include it.priorityis relative within the sitemap. Conventions:1.0— homepage0.8-0.9— primary nav / hub pages0.6-0.7— service detail pages0.3-0.5— legal pages, tag pages
- Keep it under 50,000 URLs / 50 MB. For larger sites, split into multiple sitemaps + a sitemap index.
- Update
lastmodwhenever pages are significantly rewritten. Triggers re-crawl.
3.3. Per-Page Meta Tags
Section titled “3.3. Per-Page Meta Tags”Every page needs unique <title> and <meta name="description">. For React, use react-helmet-async or equivalent. For Astro, native head slots work.
Minimum per page:
<title>Page Title — Brand Name</title><meta name="description" content="Compelling 120-160 char description that says what's on the page."><meta property="og:title" content="Page Title — Brand Name"><meta property="og:description" content="Same description (or social-tuned variant).">Rules:
- Title: 50-60 chars. Format:
Page Topic — Brand Name. - Description: 120-160 chars. Should answer “why click this result?”
- No duplicates. Every page gets unique copy. Google de-indexes near-duplicate metas.
- Avoid keyword stuffing — Google penalizes it.
3.4. Organization + LocalBusiness JSON-LD (site-wide)
Section titled “3.4. Organization + LocalBusiness JSON-LD (site-wide)”Add inside <head> in the root HTML template. This is the same on every page — covers the whole site:
<script type="application/ld+json">{ "@context": "https://schema.org", "@graph": [ { "@type": "Organization", "@id": "https://<client-domain>/#organization", "name": "<Brand Name>", "legalName": "<Legal LLC Name>", "url": "https://<client-domain>", "logo": "https://<client-domain>/logo.png", "image": "https://<client-domain>/logo.png", "email": "<contact email>", "telephone": "+1-XXX-XXX-XXXX", "address": { "@type": "PostalAddress", "addressLocality": "<City>", "addressRegion": "<2-letter state>", "addressCountry": "US" }, "sameAs": [ "https://twitter.com/<handle>", "https://www.linkedin.com/company/<slug>", "https://www.facebook.com/<handle>" ] }, { "@type": "<LocalBusiness subtype>", "@id": "https://<client-domain>/#localbusiness", "name": "<Brand Name>", "url": "https://<client-domain>", "image": "https://<client-domain>/logo.png", "telephone": "+1-XXX-XXX-XXXX", "email": "<contact email>", "priceRange": "$$", "description": "<Short business description>", "address": { "@type": "PostalAddress", "addressLocality": "<City>", "addressRegion": "<state>", "addressCountry": "US" }, "areaServed": { "@type": "Country", "name": "United States" }, "openingHoursSpecification": { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "opens": "09:00", "closes": "17:00" } }, { "@type": "WebSite", "@id": "https://<client-domain>/#website", "url": "https://<client-domain>", "name": "<Brand Name>", "publisher": { "@id": "https://<client-domain>/#organization" }, "inLanguage": "en-US" } ]}</script>Pick the right LocalBusiness subtype — more specific = better rich results:
ProfessionalService— agencies, consultants, IT services, legal, accountingStore/Restaurant/MedicalBusiness/AutoRepairetc. — when applicable- Full list: https://schema.org/LocalBusiness
sameAs is optional but valuable — Google uses it to confirm brand identity across the web. Omit any URLs the client doesn’t actually have (no placeholder fakes).
3.5. Service Schema (per service / product page)
Section titled “3.5. Service Schema (per service / product page)”Each individual service or product page should emit its own Service JSON-LD. For React, build a reusable component (see ServiceSchema.tsx on the main WTS site for reference).
Per-page Service block:
{ "@context": "https://schema.org", "@type": "Service", "name": "Service Name", "description": "What this service does, 1-2 sentences.", "url": "https://<client-domain>/services/example", "provider": { "@id": "https://<client-domain>/#organization" }, "serviceType": "Web Development", "areaServed": { "@type": "Country", "name": "United States" }, "offers": [ { "@type": "Offer", "name": "Standard Tier", "url": "https://<client-domain>/services/example", "priceCurrency": "USD", "price": "300" }, { "@type": "Offer", "name": "Advanced Tier", "url": "https://<client-domain>/services/example", "priceCurrency": "USD", "priceSpecification": { "@type": "PriceSpecification", "priceCurrency": "USD", "minPrice": "700", "maxPrice": "3000" } } ]}Rules:
providerreferences the Organization by its@id— don’t duplicate the org details.offerscan be a single object or an array. UsepriceSpecificationwith min/max for ranges; usepricefor fixed.- Skip offers with non-numeric prices (e.g. “Contact Us”). Google rejects schemas with invalid prices.
- For services with no listed price (consulting, contact-only), omit
offersentirely — that’s valid.
3.6. Open Graph + Twitter Card Fallbacks
Section titled “3.6. Open Graph + Twitter Card Fallbacks”Add to the root HTML head. Per-page Helmets override og:title and og:description; this layer provides fallback image + site name:
<meta property="og:type" content="website"><meta property="og:site_name" content="<Brand Name>"><meta property="og:image" content="https://<client-domain>/og-image.jpg"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:image" content="https://<client-domain>/og-image.jpg">Image requirements:
- 1200×630 pixels (Facebook + LinkedIn standard)
- JPG or PNG, under 5 MB
- Include brand logo + a tagline if possible
- Test the actual render: https://www.opengraph.xyz/ or https://cards-dev.twitter.com/validator
3.7. FAQ Schema (where applicable)
Section titled “3.7. FAQ Schema (where applicable)”If the site has a FAQ page with Q&A pairs, add FAQPage schema. Eligible for rich-result FAQ accordions in search results:
{ "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [ { "@type": "Question", "name": "Question text here?", "acceptedAnswer": { "@type": "Answer", "text": "Plain-text answer. HTML is allowed but stripped to text for the rich result." } } ]}Only mark up content that’s actually visible on the page. Marking up hidden FAQ items is a violation that can trigger manual action.
4. Validation Before Launch
Section titled “4. Validation Before Launch”Run these checks before flipping DNS:
| Tool | What to Check | URL |
|---|---|---|
| Rich Results Test | Every page type renders schema correctly | https://search.google.com/test/rich-results |
| Schema.org Validator | Schema syntax is valid | https://validator.schema.org/ |
| PageSpeed Insights | Core Web Vitals — fix red metrics | https://pagespeed.web.dev/ |
| OpenGraph.xyz | Social previews render with image + title | https://www.opengraph.xyz/ |
| W3C HTML Validator | No critical markup errors | https://validator.w3.org/ |
| Mobile-Friendly Test | Mobile viewport renders correctly | https://search.google.com/test/mobile-friendly |
If any of these fail, fix before submitting to GSC.
5. Post-Launch Submission Process
Section titled “5. Post-Launch Submission Process”Run these immediately after DNS propagates and the site is reachable at the canonical domain.
5.1. Google Search Console (10 min)
Section titled “5.1. Google Search Console (10 min)”- Open https://search.google.com/search-console
- Click Add Property → choose Domain property (covers both www + apex, all subdomains, both protocols).
- Enter the bare domain (e.g.
clientsite.com— nohttps://, nowww). - Google returns a TXT record. Add it in the client’s DNS dashboard (Cloudflare, GoDaddy, etc.):
- Type:
TXT - Host/Name:
@(root) - Value: the verification string Google provides
- TTL: Auto / 300
- Type:
- Wait 1-5 minutes for DNS propagation, then click Verify in GSC.
- Once verified: left sidebar → Sitemaps → enter the full URL (
https://clientsite.com/sitemap.xml) → Submit. - Request indexing on priority pages via URL Inspection in the left sidebar. Paste the full URL, wait for the inspection result, click Request Indexing. Limited to ~10 per day. Priority order: homepage → primary nav pages (services, about, contact) → flagship product/service pages.
If sitemap submission fails with “Couldn’t fetch”:
- Almost always transient. Refresh status in 30 min — usually flips to “Success” without intervention.
- If still failing after 24 hours: check that the property type matches the actual domain (Domain vs URL-prefix), and that
wwwresolves if the property iswww-flavored. - Even with “Couldn’t fetch” status, Google often still crawls the URLs inside the sitemap.
5.2. Bing Webmaster Tools (5 min)
Section titled “5.2. Bing Webmaster Tools (5 min)”Bing is ~5-10% of US search traffic and easier to optimize for (less competitive). Always set up.
- Open https://www.bing.com/webmasters
- Click Import from Google Search Console — one-click pulls your verified properties.
- The import does NOT carry over sitemap submissions. Go to Sitemaps in left sidebar → Submit sitemap → paste the full sitemap URL → submit.
- Optionally: URL Submission in sidebar → submit up to 10,000 URLs/day for verified domains (way more generous than GSC). Bulk-submit every URL on the site for fastest indexing.
Bing also supports the IndexNow protocol — implement on dynamic sites that change frequently so Bing + Yandex re-crawl in near-real-time on content updates.
5.3. Google Business Profile (if local-relevant)
Section titled “5.3. Google Business Profile (if local-relevant)”If the client has a physical location or serves a defined geographic area:
- https://business.google.com → claim or create profile.
- Fill in NAP (Name, Address, Phone), categories, hours, photos.
- Add the website URL.
- Verify NAP consistency — exact Name, exact Address format, exact Phone format must match: GBP, website footer, structured data, any directory listings.
This is the single highest-leverage move for local SEO.
5.4. Initial Backlinks (optional but valuable)
Section titled “5.4. Initial Backlinks (optional but valuable)”Get 3-5 legitimate inbound links pointing at the new site within the first 30 days:
- LinkedIn company page (free, takes 10 min)
- Facebook business page
- Industry directories (BBB, Chamber of Commerce, niche-specific listings)
- GitHub organization page if the business has any open-source presence
- Owner’s personal LinkedIn / Twitter profile linking to the site
Avoid: paid link farms, comment spam, reciprocal-link schemes (Google penalizes).
6. Monitoring (Week 2+)
Section titled “6. Monitoring (Week 2+)”After 7-14 days, Google should have crawled the priority pages. Check these reports in GSC:
| Report | What to Look For | Action if Bad |
|---|---|---|
| Pages → Indexing | ”Indexed” count growing toward total URL count | Investigate “Not indexed” reasons (duplicates, noindex, errors) |
| Performance | Impressions appearing (3-day lag) | If zero after 14 days, recheck verification + sitemap status |
| Coverage / Errors | No critical errors | Fix any 404s or server errors blocking crawl |
| Core Web Vitals | Pass / Needs Improvement | Address LCP, CLS, INP regressions |
| Manual Actions | Should be empty | If present, address immediately (rare) |
| Mobile Usability | Pass | Fix viewport / tap-target / text-size issues |
Run PageSpeed Insights monthly on the homepage + key conversion pages. Aim for ≥90 on both mobile and desktop.
7. Common Pitfalls + Fixes
Section titled “7. Common Pitfalls + Fixes”| Symptom | Cause | Fix |
|---|---|---|
| GSC sitemap status “Couldn’t fetch” | Transient crawl error or property/URL mismatch | Wait 30 min and refresh; if persistent 24h, verify property type matches DNS |
| GSC says “robots.txt prevents crawling” | Usually stale cache, sometimes a Disallow slipped in | Verify live robots.txt via curl; in GSC: Settings → robots.txt report → Request recrawl |
| Site indexed but ranks nowhere | New domain “sandbox” + thin content + no backlinks | Time + content + GBP + initial backlinks. Most lift comes in months 3-6 |
| Schema validator rejects Service | ”Contact Us” or similar in price field | Omit offers entirely for contact-only services, or use priceRange on the parent Organization |
| Social link previews show no image | og:image URL is relative or 404s | Use absolute URLs; verify the image returns 200 |
| Duplicate content warnings | Multiple URLs serving identical content (e.g. ?utm_* variants, www vs apex) | Set canonical URL via <link rel="canonical">; enforce www-or-apex with 301 redirects |
| ”Discovered - currently not indexed” | Google saw the URL but chose not to crawl it (low-priority signal) | Improve internal linking to the page; check it’s not orphaned; request indexing manually |
www subdomain doesn’t resolve | DNS doesn’t have a www CNAME | Either add www CNAME pointing to root, or set up redirect at hosting layer — pick one canonical |
8. Long-Term Ranking Moves (months 1-6)
Section titled “8. Long-Term Ranking Moves (months 1-6)”Schema + indexing get the site into Google’s database. Ranking is a separate game:
- Content depth + EEAT signals. Google rewards content written by real experts. Put author bios on long-form content. Cite sources. Update older content with
lastmodbumps. - Internal linking. Every important page should be linked from at least 3 other pages. Use descriptive anchor text (not “click here”).
- Backlinks from relevant sites. 5 quality links from industry-relevant sites outperform 100 directory listings. Strategies: guest posts, podcast appearances, partner directories, getting featured on industry news.
- Core Web Vitals. Google uses these as ranking signals. Especially LCP under 2.5s and CLS under 0.1.
- Refresh cycle. Update top pages quarterly. Bump
lastmod. Google rewards “freshness” especially for evergreen service pages. - Track + iterate. Use GSC Performance report monthly. Identify queries you’re appearing for but ranking #11-30 → optimize those pages first; they’re the closest to page 1.
9. Reference: WTS Main Site Implementation
Section titled “9. Reference: WTS Main Site Implementation”The WTS main site (wizardtechservices.com) implements every piece of this SOP. Reference code:
- robots.txt:
public/robots.txt - sitemap.xml:
public/sitemap.xml(manually maintained — could be auto-generated) - Organization + LocalBusiness schema:
client/index.html(inline<script type="application/ld+json">) - OG fallback:
client/index.htmlhead meta tags - Per-page Helmet: Every bespoke service page (e.g.
client/src/pages/services/*/*.tsx) - Service schema helper:
client/src/components/ServiceSchema.tsx— reusable component, see for the parsePrice utility - FAQ schema:
client/src/pages/info/faq/FAQPage.tsx
10. Checklist Summary
Section titled “10. Checklist Summary”Pre-launch:
-
robots.txtpresent, allows all, points at sitemap -
sitemap.xmllists every public URL withlastmod - Every page has unique
<title>+<meta description> - Organization + LocalBusiness JSON-LD in root HTML
- Service schema on each service/product page
- OG image + Twitter card fallbacks set
- FAQ schema on FAQ pages (if applicable)
- Rich Results Test passes for each page type
- PageSpeed Insights ≥80 on mobile
Post-launch (within 48 hours of DNS flip):
- GSC Domain property added + verified
- Sitemap submitted to GSC
- URL Inspection done on top 5-6 priority pages
- Bing Webmaster Tools property added + sitemap submitted
- Google Business Profile claimed (if applicable)
- 3+ initial backlinks established (LinkedIn, social, directories)
Week 2+:
- GSC Pages report shows indexed URLs growing
- Performance report shows impressions accumulating
- No errors in Coverage report
- PageSpeed Insights still passing monthly
Document History
Section titled “Document History”| Version | Date | Change |
|---|---|---|
| 1.0 | 2026-05-11 | Initial SOP based on wizardtechservices.com launch process |