DEV Community

longchun
longchun

Posted on

Protecting Email Addresses from Web Scrapers in Vue.js Applications

Introduction

Email addresses on websites are prime targets for spam bots and web scrapers. When you display email addresses in plain text or as simple mailto links, you're essentially offering them up to harvesting bots. In this article, I'll share a robust Vue.js solution we implemented to protect email addresses while maintaining usability for real users.

The Problem

In a typical footer component, email addresses are often displayed like this:

html

<a href="mailto:contact@example.com">contact@example.com</a>
Enter fullscreen mode Exit fullscreen mode

This approach is problematic because:

  • Email scrapers can easily identify the mailto pattern
  • The plaintext email address is directly accessible in the DOM
  • There's no protection layer between bots and your email

The Solution: A Three-Component Architecture

We developed a solution using three Vue.js components working together:

  1. EmailProtected.vue: A specialized component to render protected email addresses
  2. ContactItem.vue: A component that handles different types of contact information
  3. FooterContact.vue: The parent component organizing all contact information

1. The EmailProtected Component

This component is the core of our solution. It implements several anti-scraping techniques:

<template>
  <span>
    <a
      href="#"
      @click.prevent="handleClick"
      @mouseenter="generateEmail"
      class="email-protected"
      :aria-label="`Email address: ${visualEmail}`"
      tabindex="0"
      @keydown.enter="handleClick"
      @keydown.space="handleClick"
    >
      <span v-for="(part, index) in emailParts" :key="index" :class="`email-part-${index}`">{{ part }}</span>
      <span class="domain" v-if="showDomain">{{ '@' + domain }}</span>
    </a>
  </span>
</template>

<script>
export default {
  name: 'EmailProtected',
  props: {
    username: {
      type: String,
      required: true
    },
    domain: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      emailParts: [],
      showDomain: false,
      emailGenerated: false
    }
  },
  computed: {
    visualEmail() {
      return `${this.username}@${this.domain}`;
    },
    fullEmail() {
      return `${this.username}@${this.domain}`;
    }
  },
  mounted() {
    // Split username into parts to make it harder for bots to recognize as email
    const parts = [];
    for (let i = 0; i < this.username.length; i++) {
      parts.push(this.username.charAt(i));
    }
    this.emailParts = parts;
  },
  methods: {
    generateEmail() {
      if (!this.emailGenerated) {
        this.showDomain = true;
        this.emailGenerated = true;
      }
    },
    handleClick() {
      this.generateEmail();
      // Use setTimeout to delay the creation of the mailto link
      setTimeout(() => {
        window.location.href = `mailto:${this.fullEmail}`;
      }, 50);
    }
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Anti-Scraping Techniques Used

  1. Email Fragmentation: The email username is split into individual characters stored in an array, making pattern recognition more difficult.
  2. Delayed Rendering: The domain part of the email is only shown after user interaction (hover, focus, or click).
  3. Dynamic Link Creation: The mailto link is created on-demand via JavaScript instead of being present in the static HTML.
  4. CSS Obfuscation: We use CSS techniques like unicode-bidi to make automated parsing more challenging.
  5. No Static Email Strings: There's no complete email string in the HTML that could be found with regular expressions.

2. The ContactItem Component

This component handles different types of contact items including our protected emails:

vue

3. The Parent FooterContact Component

This component organizes all our contact information and uses the type-based system:

vue

How This Solution Works

  1. Data Organization: Email addresses are split into username and domain parts in the data structure
  2. Conditional Rendering: Different types of contact info are rendered differently
  3. Progressive Enhancement: Emails start partially obfuscated and are completed on user interaction
  4. User Experience: Remains intuitive for actual users (hover shows the full email, click creates a mailto link)
  5. Accessibility: Maintains keyboard navigation and screen reader support

Additional Techniques You Can Add

Our solution is solid, but you could enhance it further with these techniques:

  1. Server-side Rendering Considerations: Add data-attributes that are only processed client-side
  2. Unicode Character Substitution: Replace some characters with similar-looking Unicode alternatives
  3. Image or SVG Emails: Convert emails to images or SVGs for critical cases
  4. Contact Forms: Provide a form alternative to directly exposing emails
  5. CAPTCHA for Email Visibility: Require CAPTCHA completion before revealing email addresses

Browser Compatibility and Accessibility Considerations

Our solution works well across modern browsers and maintains accessibility through:

  • Proper semantic HTML (<address> elements for contact info)
  • ARIA attributes for screen readers
  • Keyboard navigation support
  • Focus states and clear interactive elements

Conclusion

By implementing this three-component architecture, you can significantly reduce the risk of email harvesting without compromising user experience. The techniques combine frontend obfuscation strategies that make automated scraping difficult while keeping emails accessible to actual visitors.

While no solution is 100% bot-proof, these techniques create multiple layers of protection that will deter most scraping attempts. The modular approach also allows you to easily update your protection methods as spam techniques evolve.

Remember that email protection is a continuous process, and it's good practice to monitor your spam levels and adjust your protection strategies accordingly.


What anti-scraping techniques have you implemented in your projects? Share your experiences in the comments below.

Top comments (0)