# Schema Markup Implementation Guide for RateRight

## Overview

This guide provides comprehensive instructions for implementing schema.org structured data on RateRight's website to enhance search visibility and enable rich results. We'll cover four main schema types essential for RateRight's business: JobPosting, LocalBusiness, Organization, and FAQPage.

## Table of Contents

1. [Implementation Best Practices](#implementation-best-practices)
2. [JobPosting Schema](#jobposting-schema)
3. [LocalBusiness Schema](#localbusiness-schema)
4. [Organization Schema](#organization-schema)
5. [FAQPage Schema](#faqpage-schema)
6. [Testing and Validation](#testing-and-validation)
7. [Common Mistakes to Avoid](#common-mistakes-to-avoid)
8. [Impact on Search Results](#impact-on-search-results)

## Implementation Best Practices

### General Guidelines

1. **JSON-LD Format**: Use JSON-LD (JavaScript Object Notation for Linked Data) as it's Google's preferred format
2. **Placement**: Add schema markup in the `<head>` section of your HTML pages
3. **Dynamic Content**: For frequently changing content (like job postings), implement dynamic generation
4. **Validation**: Always test your markup using Google's Rich Results Test tool
5. **Relevance**: Only mark up content that is visible to users on the page

### Technical Requirements

- Use valid JSON syntax
- Include all required properties for each schema type
- Use ISO 8601 format for dates (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)
- Include country codes (ISO 3166-1 alpha-2) for addresses
- Use HTTPS URLs where applicable

## JobPosting Schema

### Required Properties
- `title`: Job title only (no codes, addresses, or salaries)
- `description`: Full HTML-formatted job description
- `datePosted`: Original posting date in ISO 8601 format
- `hiringOrganization`: Company information
- `jobLocation`: Physical location where employee will work

### Recommended Properties
- `employmentType`: FULL_TIME, PART_TIME, CONTRACTOR, TEMPORARY, INTERN
- `baseSalary`: Salary information
- `validThrough`: Expiration date
- `identifier`: Internal job ID
- `directApply`: Boolean for direct application

### Dynamic Job Posting Component

Create `components/schema/job-posting.tsx`:

```tsx
import React from 'react';

interface JobPostingSchemaProps {
  title: string;
  description: string;
  datePosted: string;
  validThrough?: string;
  employmentType?: string[];
  hiringOrganization: {
    name: string;
    sameAs?: string;
    logo?: string;
  };
  jobLocation: {
    streetAddress?: string;
    addressLocality: string;
    addressRegion: string;
    postalCode: string;
    addressCountry: string;
  };
  baseSalary?: {
    currency: string;
    minValue?: number;
    maxValue?: number;
    value?: number;
    unitText: 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR';
  };
  identifier?: string;
  isRemote?: boolean;
  applicantLocationRequirements?: string[];
}

export const JobPostingSchema: React.FC<JobPostingSchemaProps> = ({
  title,
  description,
  datePosted,
  validThrough,
  employmentType = ['FULL_TIME'],
  hiringOrganization,
  jobLocation,
  baseSalary,
  identifier,
  isRemote = false,
  applicantLocationRequirements
}) => {
  const schemaData = {
    '@context': 'https://schema.org/',
    '@type': 'JobPosting',
    title,
    description: `<p>${description}</p>`,
    datePosted,
    validThrough,
    employmentType,
    hiringOrganization: {
      '@type': 'Organization',
      name: hiringOrganization.name,
      ...(hiringOrganization.sameAs && { sameAs: hiringOrganization.sameAs }),
      ...(hiringOrganization.logo && { logo: hiringOrganization.logo })
    },
    ...(identifier && {
      identifier: {
        '@type': 'PropertyValue',
        name: hiringOrganization.name,
        value: identifier
      }
    }),
    ...(isRemote && {
      jobLocationType: 'TELECOMMUTE',
      ...(applicantLocationRequirements && {
        applicantLocationRequirements: applicantLocationRequirements.map(country => ({
          '@type': 'Country',
          name: country
        }))
      })
    }),
    ...(!isRemote && {
      jobLocation: {
        '@type': 'Place',
        address: {
          '@type': 'PostalAddress',
          ...jobLocation
        }
      }
    }),
    ...(baseSalary && {
      baseSalary: {
        '@type': 'MonetaryAmount',
        currency: baseSalary.currency,
        value: {
          '@type': 'QuantitativeValue',
          ...(baseSalary.value !== undefined && { value: baseSalary.value }),
          ...(baseSalary.minValue !== undefined && { minValue: baseSalary.minValue }),
          ...(baseSalary.maxValue !== undefined && { maxValue: baseSalary.maxValue }),
          unitText: baseSalary.unitText
        }
      }
    })
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
    />
  );
};

// Usage example:
/*
<JobPostingSchema
  title="Construction Project Manager"
  description="We are seeking an experienced Construction Project Manager to join our team..."
  datePosted="2024-01-15"
  validThrough="2024-03-15"
  employmentType={['FULL_TIME']}
  hiringOrganization={{
    name: 'RateRight',
    sameAs: 'https://rateright.com.au',
    logo: 'https://rateright.com.au/logo.png'
  }}
  jobLocation={{
    streetAddress: '123 Construction Ave',
    addressLocality: 'Sydney',
    addressRegion: 'NSW',
    postalCode: '2000',
    addressCountry: 'AU'
  }}
  baseSalary={{
    currency: 'AUD',
    minValue: 120000,
    maxValue: 150000,
    unitText: 'YEAR'
  }}
  identifier="RR-PM-2024-001"
/>
*/
```

## LocalBusiness Schema

### Required Properties
- `@type`: Use specific business type (e.g., "ProfessionalService", "ConstructionBusiness")
- `name`: Business name
- `address`: Complete physical address

### Recommended Properties
- `telephone`: Contact number with country code
- `url`: Website URL
- `geo`: Geographic coordinates
- `openingHoursSpecification`: Business hours
- `priceRange`: Price indicator ($ to $$$$)
- `aggregateRating`: Customer ratings

### Local Business Component

Create `components/schema/local-business.tsx`:

```tsx
import React from 'react';

interface LocalBusinessSchemaProps {
  type: string; // e.g., 'ProfessionalService', 'ConstructionBusiness'
  name: string;
  description?: string;
  url: string;
  telephone: string;
  email?: string;
  address: {
    streetAddress: string;
    addressLocality: string;
    addressRegion: string;
    postalCode: string;
    addressCountry: string;
  };
  geo?: {
    latitude: number;
    longitude: number;
  };
  openingHours?: Array<{
    dayOfWeek: string[];
    opens: string;
    closes: string;
  }>;
  priceRange?: string;
  image?: string[];
}

export const LocalBusinessSchema: React.FC<LocalBusinessSchemaProps> = ({
  type,
  name,
  description,
  url,
  telephone,
  email,
  address,
  geo,
  openingHours,
  priceRange,
  image
}) => {
  const schemaData = {
    '@context': 'https://schema.org',
    '@type': type,
    name,
    ...(description && { description }),
    url,
    telephone,
    ...(email && { email }),
    address: {
      '@type': 'PostalAddress',
      ...address
    },
    ...(geo && {
      geo: {
        '@type': 'GeoCoordinates',
        ...geo
      }
    }),
    ...(openingHours && {
      openingHoursSpecification: openingHours.map(schedule => ({
        '@type': 'OpeningHoursSpecification',
        dayOfWeek: schedule.dayOfWeek,
        opens: schedule.opens,
        closes: schedule.closes
      }))
    }),
    ...(priceRange && { priceRange }),
    ...(image && { image })
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
    />
  );
};

// Usage example:
/*
<LocalBusinessSchema
  type="ConstructionBusiness"
  name="RateRight"
  description="Australia's leading construction cost estimation service"
  url="https://rateright.com.au"
  telephone="+61-468-087-171"
  email="info@rateright.com.au"
  address={{
    streetAddress: 'Level 5, 123 Builder St',
    addressLocality: 'Sydney',
    addressRegion: 'NSW',
    postalCode: '2000',
    addressCountry: 'AU'
  }}
  geo={{ latitude: -33.8688, longitude: 151.2093 }}
  openingHours={[
    {
      dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
      opens: '08:00',
      closes: '18:00'
    }
  ]}
  priceRange="$$"
  image={[
    'https://rateright.com.au/logo-16x9.jpg',
    'https://rateright.com.au/logo-4x3.jpg',
    'https://rateright.com.au/logo-1x1.jpg'
  ]}
/>
*/
```

## Organization Schema

### Recommended Properties
- `name`: Organization name
- `url`: Primary website
- `logo`: Company logo
- `description`: Business description
- `contactPoint`: Contact information
- `sameAs`: Social media profiles
- `address`: Business address
- `foundingDate`: Year established
- `numberOfEmployees`: Company size

### Organization Component

Create `components/schema/organization.tsx`:

```tsx
import React from 'react';

interface OrganizationSchemaProps {
  name: string;
  legalName?: string;
  url: string;
  logo: string;
  description: string;
  foundingDate?: string;
  email?: string;
  telephone?: string;
  address?: {
    streetAddress?: string;
    addressLocality: string;
    addressRegion: string;
    postalCode: string;
    addressCountry: string;
  };
  contactPoint?: {
    telephone: string;
    email: string;
    contactType: string;
  };
  sameAs?: string[];
  numberOfEmployees?: {
    minValue?: number;
    maxValue?: number;
    value?: number;
  };
  vatID?: string;
  taxID?: string;
}

export const OrganizationSchema: React.FC<OrganizationSchemaProps> = ({
  name,
  legalName,
  url,
  logo,
  description,
  foundingDate,
  email,
  telephone,
  address,
  contactPoint,
  sameAs,
  numberOfEmployees,
  vatID,
  taxID
}) => {
  const schemaData = {
    '@context': 'https://schema.org',
    '@type': 'Organization',
    name,
    ...(legalName && { legalName }),
    url,
    logo,
    description,
    ...(foundingDate && { foundingDate }),
    ...(email && { email }),
    ...(telephone && { telephone }),
    ...(address && {
      address: {
        '@type': 'PostalAddress',
        ...address
      }
    }),
    ...(contactPoint && {
      contactPoint: {
        '@type': 'ContactPoint',
        ...contactPoint
      }
    }),
    ...(sameAs && { sameAs }),
    ...(numberOfEmployees && {
      numberOfEmployees: {
        '@type': 'QuantitativeValue',
        ...numberOfEmployees
      }
    }),
    ...(vatID && { vatID }),
    ...(taxID && { taxID })
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
    />
  );
};

// Usage example:
/*
<OrganizationSchema
  name="RateRight"
  legalName="RateRight Australia Pty Ltd"
  url="https://rateright.com.au"
  logo="https://rateright.com.au/logo.png"
  description="RateRight is Australia's leading construction cost estimation platform, providing accurate and reliable cost estimates for construction projects across the country."
  foundingDate="2020"
  email="info@rateright.com.au"
  telephone="+61-468-087-171"
  address={{
    addressLocality: 'Sydney',
    addressRegion: 'NSW',
    postalCode: '2000',
    addressCountry: 'AU'
  }}
  contactPoint={{
    telephone: '+61-468-087-171',
    email: 'info@rateright.com.au',
    contactType: 'customer service'
  }}
  sameAs={[
    'https://www.facebook.com/RateRightAU',
    'https://www.linkedin.com/company/rateright-australia',
    'https://twitter.com/RateRightAU'
  ]}
  numberOfEmployees={{ minValue: 10, maxValue: 50 }}
/>
*/
```

## FAQPage Schema

### Required Properties
- `mainEntity`: Array of Question objects
- `name`: Full question text
- `acceptedAnswer`: Answer object
- `text`: Full answer text

### FAQ Page Component

Create `components/schema/faq-page.tsx`:

```tsx
import React from 'react';

interface FAQItem {
  question: string;
  answer: string;
}

interface FAQPageSchemaProps {
  faqs: FAQItem[];
}

export const FAQPageSchema: React.FC<FAQPageSchemaProps> = ({ faqs }) => {
  const schemaData = {
    '@context': 'https://schema.org',
    '@type': 'FAQPage',
    mainEntity: faqs.map(faq => ({
      '@type': 'Question',
      name: faq.question,
      acceptedAnswer: {
        '@type': 'Answer',
        text: faq.answer
      }
    }))
  };

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
    />
  );
};

// Usage example:
/*
const faqs = [
  {
    question: "How accurate are RateRight's cost estimates?",
    answer: "RateRight's estimates are based on current market data and have an accuracy rate of 95% for projects under $1 million."
  },
  {
    question: "What types of construction projects does RateRight support?",
    answer: "RateRight supports residential, commercial, and industrial construction projects of all sizes."
  },
  {
    question: "How quickly can I get a cost estimate?",
    answer: "Most estimates are generated within minutes of submitting your project details through our platform."
  }
];

<FAQPageSchema faqs={faqs} />
*/
```

## Testing and Validation

### Google's Rich Results Test

1. Visit: https://search.google.com/test/rich-results
2. Enter your URL or paste your code
3. Check for errors and warnings
4. Preview how your page might appear in search results

### Schema Markup Validator

1. Visit: https://validator.schema.org/
2. Test your structured data syntax
3. Ensure all required properties are present

### Implementation Checklist

- [ ] All required properties included
- [ ] Dates in ISO 8601 format
- [ ] Country codes using ISO 3166-1 alpha-2
- [ ] URLs use HTTPS protocol
- [ ] Content matches visible page content
- [ ] No keyword stuffing in descriptions
- [ ] Proper HTML formatting in descriptions
- [ ] All URLs are crawlable and accessible

## Common Mistakes to Avoid

### JobPosting Schema
1. **Including salary in title**: Keep job titles clean
2. **Missing expiration dates**: Always include validThrough for time-sensitive postings
3. **Incorrect location data**: jobLocation must be where employee reports to work
4. **Generic descriptions**: Provide detailed, unique job descriptions
5. **Multiple locations in one posting**: Create separate postings for each location

### LocalBusiness Schema
1. **Using generic @type**: Use specific business types (ConstructionBusiness vs LocalBusiness)
2. **Missing phone numbers**: Include country code (+61 for Australia)
3. **Inconsistent NAP**: Ensure Name, Address, Phone are consistent across all listings
4. **Forgetting geo coordinates**: Add latitude/longitude for better local search
5. **Outdated hours**: Keep opening hours current and include seasonal variations

### Organization Schema
1. **Missing social profiles**: Include all active social media sameAs links
2. **Logo issues**: Ensure logo meets Google's requirements (112x112px minimum)
3. **Outdated information**: Regularly update contact details and employee count
4. **Missing identifiers**: Include ABN/ACN for Australian businesses

### FAQPage Schema
1. **Advertising in FAQs**: Keep content informational, not promotional
2. **Multiple answers per question**: Use QAPage schema for user-generated answers
3. **Hidden content**: Ensure all FAQ content is visible to users
4. **Repetitive FAQs**: Mark up only one instance of repeated questions

## Impact on Search Results

### Rich Results Benefits

1. **Enhanced Visibility**: Stand out in search results with rich snippets
2. **Improved CTR**: Rich results typically have higher click-through rates
3. **Voice Search**: Better performance in voice search results
4. **Knowledge Panel**: Enhanced company information in knowledge panels
5. **Job Search**: Eligibility for Google Jobs search experience

### Expected Improvements

- **Job Postings**: 25-40% increase in applications through Google Jobs
- **Local Search**: 20-30% improvement in local search visibility
- **Brand Search**: Enhanced knowledge panel with accurate information
- **FAQ Search**: Potential featured snippets for common questions

### Monitoring Performance

Use Google Search Console to track:
- Rich result impressions and clicks
- Average position for marked-up content
- Errors and warnings in structured data
- Manual actions related to markup

## Implementation Timeline

### Phase 1: Foundation (Week 1)
- Implement Organization schema on homepage
- Add LocalBusiness schema to contact/about pages

### Phase 2: Content Enhancement (Week 2)
- Create FAQPage schema for existing FAQ content
- Implement JobPosting schema for career pages

### Phase 3: Optimization (Week 3-4)
- Test all implementations with validation tools
- Monitor performance in Search Console
- Make adjustments based on results

### Phase 4: Maintenance (Ongoing)
- Regular validation checks
- Updates when content changes
- Seasonal updates for hours/special events

## Additional Resources

- [Google's Structured Data Guidelines](https://developers.google.com/search/docs/appearance/structured-data/sd-policies)
- [Schema.org Documentation](https://schema.org/)
- [Rich Results Test Tool](https://search.google.com/test/rich-results)
- [Search Console Help](https://support.google.com/webmasters/answer/7576553)

## Next Steps

1. Review current website structure
2. Identify pages that need schema markup
3. Implement components following this guide
4. Test thoroughly before deployment
5. Monitor performance and iterate

Remember: Structured data is not a one-time implementation. Keep your markup updated as your business evolves and Google updates its guidelines.