<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Cyris ⭐️ JavaScript</title>
    <description>The latest articles on Forem by Cyris ⭐️ JavaScript (@sudo_overflow).</description>
    <link>https://forem.com/sudo_overflow</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F391317%2F569f94b0-9bc3-4ee6-88a3-9a69e73cb770.jpg</url>
      <title>Forem: Cyris ⭐️ JavaScript</title>
      <link>https://forem.com/sudo_overflow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sudo_overflow"/>
    <language>en</language>
    <item>
      <title>Making Your Documentation AI-Friendly: A Practical Guide</title>
      <dc:creator>Cyris ⭐️ JavaScript</dc:creator>
      <pubDate>Fri, 11 Apr 2025 21:57:26 +0000</pubDate>
      <link>https://forem.com/sudo_overflow/making-your-documentation-ai-friendly-a-practical-guide-2h1f</link>
      <guid>https://forem.com/sudo_overflow/making-your-documentation-ai-friendly-a-practical-guide-2h1f</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu6kbletjcaclrrc91ve3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu6kbletjcaclrrc91ve3.png" alt="Making Your Documentation AI-Friendly: A Practical Guide" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey there! 👋   I've been working with AI and web scraping for a while now, and I want to share some practical tips on how to make your documentation more AI-friendly. Instead of trying to block all bots (which never really works), let's focus on creating structured, reliable channels for beneficial AI while keeping the bad actors out.&lt;/p&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  📅 The LLM Knowledge Gap
&lt;/h2&gt;

&lt;p&gt;Here's the thing about Large Language Models - they're only as good as their training data. Most LLMs have a knowledge cutoff date, meaning they don't know anything that happened after their last training session.&lt;/p&gt;

&lt;p&gt;This creates a real problem when users ask about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New features released after the cutoff&lt;/li&gt;
&lt;li&gt;Breaking changes in recent updates&lt;/li&gt;
&lt;li&gt;Security patches and bug fixes&lt;/li&gt;
&lt;li&gt;Current best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why it's crucial to give LLMs direct access to your latest documentation. When an LLM can pull in your current docs on demand, it can provide accurate, up-to-date information instead of relying on potentially outdated training data.&lt;/p&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  🤔 Why AI Can't Read Your Docs
&lt;/h2&gt;

&lt;p&gt;Before we dive into solutions, let's look at the most common roadblocks I've seen that prevent AI from properly accessing documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client-Side Rendering&lt;/strong&gt;: Some AI crawlers can't execute JavaScript, so if your content is rendered client-side, it's invisible to them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex Navigation&lt;/strong&gt;: JavaScript-based routing and hash navigation break traditional crawling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inconsistent Structure&lt;/strong&gt;: Varying HTML layouts and tags make it hard for AI to understand content relationships&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication Walls&lt;/strong&gt;: Login requirements block automated access completely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limiting&lt;/strong&gt;: Aggressive rate limits prevent thorough documentation crawling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-Bot Measures&lt;/strong&gt;: CAPTCHAs and similar tools block all automated access, good or bad&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Issues&lt;/strong&gt;: Slow loading times and timeouts interrupt the crawling process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news? All of these problems have solutions, and that's what we're going to cover next. Let's make your docs AI-friendly!&lt;/p&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 Roll Out the Welcome Mat for Good AI
&lt;/h2&gt;

&lt;p&gt;Instead of making bots guess their way through your site, let's make it crystal clear who's welcome. Here's how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Allowlisting&lt;/strong&gt;: Use &lt;code&gt;robots.txt&lt;/code&gt; to explicitly allow trusted bots. Set up your WAF or firewall to recognize these good actors by their user agents and IP ranges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Double-Check&lt;/strong&gt;: Don't just trust the user agent string. Verify bots by:

&lt;ul&gt;
&lt;li&gt;Checking their IP ownership&lt;/li&gt;
&lt;li&gt;Using verified bot lists (like Cloudflare's)&lt;/li&gt;
&lt;li&gt;Looking out for new verification standards&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Clear Signals&lt;/strong&gt;: Use &lt;code&gt;robots.txt&lt;/code&gt; to publicly declare which bots you're cool with, even if you're enforcing rules elsewhere.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 Common AI Crawlers You'll Encounter
&lt;/h2&gt;

&lt;p&gt;First things first - you need to know who's knocking at your door. While &lt;code&gt;robots.txt&lt;/code&gt; isn't perfect, it's still the standard way to signal your intentions. Here's a quick reference of the main players:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bot&lt;/th&gt;
&lt;th&gt;User-Agent&lt;/th&gt;
&lt;th&gt;What They Do&lt;/th&gt;
&lt;th&gt;Allow Example&lt;/th&gt;
&lt;th&gt;Block Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI (ChatGPT)&lt;/td&gt;
&lt;td&gt;GPTBot&lt;/td&gt;
&lt;td&gt;Web crawling for model improvement&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: GPTBot&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: GPTBot&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI (Plugin)&lt;/td&gt;
&lt;td&gt;ChatGPT-User&lt;/td&gt;
&lt;td&gt;Plugin actions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: ChatGPT-User&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: ChatGPT-User&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google AI&lt;/td&gt;
&lt;td&gt;Google-Extended&lt;/td&gt;
&lt;td&gt;Gemini, Vertex AI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: Google-Extended&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: Google-Extended&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;anthropic-ai&lt;/td&gt;
&lt;td&gt;General crawling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: anthropic-ai&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: anthropic-ai&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perplexity&lt;/td&gt;
&lt;td&gt;PerplexityBot&lt;/td&gt;
&lt;td&gt;Web crawling&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: PerplexityBot&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: PerplexityBot&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Common Crawl&lt;/td&gt;
&lt;td&gt;CCBot&lt;/td&gt;
&lt;td&gt;Public web data&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: CCBot&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: CCBot&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;FacebookBot&lt;/td&gt;
&lt;td&gt;Platform needs&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: FacebookBot&amp;lt;br&amp;gt;Allow: /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;User-agent: FacebookBot&amp;lt;br&amp;gt;Disallow: /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This list changes often, and remember - &lt;code&gt;robots.txt&lt;/code&gt; is more of a suggestion than a rule.&lt;/p&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  📦 Give AI Direct Access to Your Data
&lt;/h2&gt;

&lt;p&gt;The best way to ensure AI gets your docs right? Give them direct access to structured data. Here's how to do it:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Use llms.txt
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/AnswerDotAI/llms-txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; standard is a game-changer for making your site AI-friendly. It's like a &lt;code&gt;robots.txt&lt;/code&gt; for LLMs, but way more powerful. Here's why you should use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardized Format&lt;/strong&gt;: A simple markdown file that tells LLMs exactly how to use your site&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to Implement&lt;/strong&gt;: Just add a &lt;code&gt;/llms.txt&lt;/code&gt; file to your root directory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human &amp;amp; Machine Readable&lt;/strong&gt;: Works for both humans and AI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-Aware&lt;/strong&gt;: Helps LLMs understand your site's structure and purpose&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown Support&lt;/strong&gt;: Automatically serves markdown versions of your pages (just add &lt;code&gt;.md&lt;/code&gt; to URLs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a quick example of what your &lt;code&gt;llms.txt&lt;/code&gt; might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Your Project Name&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; A brief description of what your project does and why it's awesome&lt;/span&gt;

&lt;span class="gu"&gt;## Documentation&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Getting Started&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://your-site.com/getting-started.html.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Quick start guide
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;API Reference&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://your-site.com/api.html.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Complete API documentation

&lt;span class="gu"&gt;## Optional&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Contributing Guide&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://your-site.com/contributing.html.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: How to contribute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best part? It's super simple to implement and works with existing tools. Check out the &lt;a href="https://github.com/AnswerDotAI/llms-txt" rel="noopener noreferrer"&gt;llms.txt GitHub repo&lt;/a&gt; for more details and examples.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Build an API and MCP server
&lt;/h3&gt;

&lt;p&gt;Create a public API specifically for your documentation. This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean, structured data in JSON/Markdown&lt;/li&gt;
&lt;li&gt;More reliable than HTML scraping&lt;/li&gt;
&lt;li&gt;Better control over access&lt;/li&gt;
&lt;li&gt;Easier for AI devs to find and use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here's the cool part - you can take it a step further by setting up an MCP (Model Context Protocol) server. This lets AI models request documentation directly from your source, ensuring they always get the latest version. Here's how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example MCP server setup&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// MCP endpoint for documentation&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/mcp/docs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Fetch latest docs from your source&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchLatestDocs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;section&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Return in a format AI can easily consume&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;lastUpdated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-docs-repo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Start the server&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MCP server running on port 3000&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefits of using an MCP server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always Fresh&lt;/strong&gt;: AI gets the latest docs directly from your source&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt;: Specify which version of docs you want&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured Access&lt;/strong&gt;: Clean, consistent data format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt;: Track which parts of your docs are most useful to AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can even combine this with your &lt;code&gt;llms.txt&lt;/code&gt; file to point AI to your MCP endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Your Project Docs&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; Latest documentation available via MCP&lt;/span&gt;

&lt;span class="gu"&gt;## API Endpoints&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;MCP Documentation Server&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://your-site.com/mcp/docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Get the latest docs
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;MCP Search&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://your-site.com/mcp/search&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Search across all versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach gives you the best of both worlds - structured access through an API and real-time updates through MCP. The AI can request exactly what it needs, when it needs it, and you maintain complete control over the content.&lt;/p&gt;

&lt;p&gt;Just look at how all these companies and users building 3rd party tools are leveraging MCP servers. &lt;a href="https://mcp.so/" rel="noopener noreferrer"&gt;MCP Server Directory&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Provide Direct Markdown Access
&lt;/h3&gt;

&lt;p&gt;Sometimes the simplest solution is the best. You can make your documentation directly available as markdown files that AI can easily consume. Here's how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single File Approach&lt;/strong&gt;: Create a comprehensive &lt;code&gt;docs.md&lt;/code&gt; that links to all your documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downloadable Archive&lt;/strong&gt;: Offer a &lt;code&gt;.zip&lt;/code&gt; of all your docs in markdown format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub/GitLab&lt;/strong&gt;: Host your docs in a public repository (many LLMs can read these directly)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a quick example of a well-structured markdown index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Documentation&lt;/span&gt;

&lt;span class="gu"&gt;## Core Features&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Getting Started&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;getting-started.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;API Reference&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;api.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;config.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="gu"&gt;## Advanced Topics&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Architecture&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;architecture.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Performance Tuning&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;performance.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Security&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;security.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, there are some challenges to watch out for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt;: Markdown files can get out of sync with your main docs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Limits&lt;/strong&gt;: Some docs might exceed LLM context windows (e.g., GPT-4's 128k limit)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formatting Issues&lt;/strong&gt;: Complex markdown features might not render correctly in all LLMs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link Management&lt;/strong&gt;: Relative links might break when files are moved or downloaded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To mitigate these issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a docs-as-code approach with automated sync&lt;/li&gt;
&lt;li&gt;Split large docs into smaller, focused files&lt;/li&gt;
&lt;li&gt;Keep markdown simple and well-structured&lt;/li&gt;
&lt;li&gt;Use absolute URLs for external links&lt;/li&gt;
&lt;li&gt;Include a last-updated timestamp in each file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  🏗️ Structure Your Content for Machines
&lt;/h2&gt;

&lt;p&gt;Make your docs easy for machines to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use Semantic HTML&lt;/strong&gt;: &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;section&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; - not just endless &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Schema.org&lt;/strong&gt;: Give machines explicit semantic info&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep It Clean&lt;/strong&gt;: Remove ads, repeated nav links, and cookie banners from the main content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  🔧 Fix Technical Roadblocks
&lt;/h2&gt;

&lt;p&gt;Make sure bots can actually get to your content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rendering&lt;/strong&gt;: If you're heavy on JavaScript, use SSR or Dynamic Rendering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation&lt;/strong&gt;: Use standard &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; tags and provide sitemaps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Examples&lt;/strong&gt;: Make them accessible text with clear language tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning&lt;/strong&gt;: Make it clear in URLs and metadata&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Speed&lt;/strong&gt;: Optimize for quick loading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  🛡️ Anti-Scraping vs. AI: What Works?
&lt;/h2&gt;

&lt;p&gt;Here's the lowdown on different techniques and how they stack up against AI scrapers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;How It Works&lt;/th&gt;
&lt;th&gt;Against Basic Bots&lt;/th&gt;
&lt;th&gt;Against AI&lt;/th&gt;
&lt;th&gt;What to Do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;robots.txt&lt;/td&gt;
&lt;td&gt;Voluntary rules&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Use Allow for good bots; combine with other methods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meta Tags&lt;/td&gt;
&lt;td&gt;Page-level instructions&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Use sparingly; doesn't stop scraping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAPTCHA&lt;/td&gt;
&lt;td&gt;Human verification&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Avoid on public docs; allowlist good bots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate Limiting&lt;/td&gt;
&lt;td&gt;IP-based limits&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Higher limits for verified bots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UA Filtering&lt;/td&gt;
&lt;td&gt;Block by user agent&lt;/td&gt;
&lt;td&gt;Low-Moderate&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;td&gt;Mainly for allowing good bots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JS Rendering&lt;/td&gt;
&lt;td&gt;Client-side content&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Use SSR for bots; provide APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex Nav&lt;/td&gt;
&lt;td&gt;Non-standard links&lt;/td&gt;
&lt;td&gt;Moderate-High&lt;/td&gt;
&lt;td&gt;Low-Moderate&lt;/td&gt;
&lt;td&gt;Stick to standard links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inconsistent HTML&lt;/td&gt;
&lt;td&gt;Varying structure&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low-Moderate&lt;/td&gt;
&lt;td&gt;Use consistent templates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Login Walls&lt;/td&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Keep public access; API for trusted AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fingerprinting&lt;/td&gt;
&lt;td&gt;Browser ID&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Moderate-High&lt;/td&gt;
&lt;td&gt;Part of layered defense&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Behavior Analysis&lt;/td&gt;
&lt;td&gt;Pattern detection&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Tune carefully&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bot Management&lt;/td&gt;
&lt;td&gt;Multi-signal detection&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;High-Very High&lt;/td&gt;
&lt;td&gt;Configure allowlisting&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remember&lt;/strong&gt;: The goal isn't to block all bots - it's to welcome the good ones while keeping the bad ones out. With these strategies, you can make your documentation both AI-friendly and secure.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Tools That Bypass Restrictions
&lt;/h2&gt;

&lt;p&gt;While we've focused on making your docs AI-friendly, it's worth noting that some companies are building tools to bypass these restrictions. For example, &lt;a href="https://www.firecrawl.dev/" rel="noopener noreferrer"&gt;Firecrawl&lt;/a&gt; offers a service that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle JavaScript-heavy sites&lt;/li&gt;
&lt;li&gt;Bypass rate limiting&lt;/li&gt;
&lt;li&gt;Parse dynamic content&lt;/li&gt;
&lt;li&gt;Convert web pages to LLM-ready data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, these services come with a subscription cost and can be blocked by more sophisticated anti-bot measures. That's why implementing the solutions we've discussed is still the best long-term approach - it's more reliable, cost-effective, and maintains control over how your documentation is accessed.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;Thanks for reading! If you found this guide helpful give it a ⭐️, and feel free to share it with others. If you notice any inaccuracies, please do let me know. I welcome all feedback. Hit me up on &lt;a href="https://twitter.com/sudo_overflow" rel="noopener noreferrer"&gt;X/Twitter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. Don't forget to check out the &lt;a href="https://mcp.so/" rel="noopener noreferrer"&gt;MCP Server Directory&lt;/a&gt; to see how others are implementing these strategies in the wild.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>Reverse engineering a private API with MITM Proxy</title>
      <dc:creator>Cyris ⭐️ JavaScript</dc:creator>
      <pubDate>Thu, 20 Aug 2020 09:30:02 +0000</pubDate>
      <link>https://forem.com/sudo_overflow/reverse-engineering-a-private-api-with-mitm-proxy-20ia</link>
      <guid>https://forem.com/sudo_overflow/reverse-engineering-a-private-api-with-mitm-proxy-20ia</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer: This is for educational purposes, do not abuse this method.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Developers can often struggle to find the data they need for their personal projects due to certain services locking down their API's. &lt;/p&gt;

&lt;p&gt;So I wanted to show you a way you can get the data you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you prefer video, I've got you covered.
&lt;/h3&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/xQGC-8ojYbU"&gt;
&lt;/iframe&gt;
 &lt;/p&gt;
&lt;h3&gt;
  
  
  What is MITM Proxy and how does it work?
&lt;/h3&gt;

&lt;p&gt;MITM stands for Man-in-the-Middle and &lt;strong&gt;mitmproxy&lt;/strong&gt; is exactly that. It allows you to send data through a proxy that acts as a man-in-the-middle so you can read the request and responses being sent to and from the server. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frqv5ef2sxsgm150ejrk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Frqv5ef2sxsgm150ejrk3.png" alt="Man-in-the-middle example"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Before we begin
&lt;/h3&gt;

&lt;p&gt;We'll be installing this on a Macbook and capturing the data sent from an iPhone. However &lt;strong&gt;mitmproxy&lt;/strong&gt; supports multiple platforms and devices. You can find out more in their &lt;a href="https://docs.mitmproxy.org/stable/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;. &lt;/p&gt;
&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Head on over to &lt;a href="https://mitmproxy.org/" rel="noopener noreferrer"&gt;mitmproxy.org&lt;/a&gt; and follow their installation instructions. You should be able to open &lt;strong&gt;mitmproxy&lt;/strong&gt; from the terminal with:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ mitmweb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mitmweb&lt;/strong&gt; is the GUI version that should open up in a new tab once it's running.&lt;/p&gt;
&lt;h3&gt;
  
  
  With mitmweb running we can configure our device
&lt;/h3&gt;

&lt;p&gt;You are going to need the local IP address of the computer running &lt;strong&gt;mitmproxy&lt;/strong&gt;. You can &lt;a href="https://www.google.com/search?q=how+to+find+local+ip+address&amp;amp;rlz=1C5CHFA_enNZ909NZ909&amp;amp;oq=How+to+find+local+IP+address&amp;amp;aqs=chrome.0.0l8.3270j0j7&amp;amp;sourceid=chrome&amp;amp;ie=UTF-8" rel="noopener noreferrer"&gt;Google&lt;/a&gt; how to find this on your computer. &lt;/p&gt;

&lt;p&gt;On your device follow these instructions:&lt;br&gt;
&lt;strong&gt;(Detailed tutorial in video above)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable the proxy setting on your devices connection sections. &lt;/li&gt;
&lt;li&gt;Point the IP address to the &lt;strong&gt;local IP address&lt;/strong&gt; of your computer&lt;/li&gt;
&lt;li&gt;Set the port number to &lt;strong&gt;8080&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Authentication is not needed&lt;/li&gt;
&lt;li&gt;Open your mobile browser (iPhone must be Safari)&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="http://mitm.it" rel="noopener noreferrer"&gt;mitm.it&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click to download the certificate for your device type&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;On iPhone you then need to visit the setting page. You will be prompted about the recently downloaded certificate.&lt;/em&gt; Confirm the installation of the certificate there. &lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Bypassing iPhone's extra security
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to Settings -&amp;gt; General -&amp;gt; About -&amp;gt; Certificate Trust Settings&lt;/li&gt;
&lt;li&gt;Enable full trust of root certificates. &lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Setup is done, on to the fun stuff
&lt;/h2&gt;

&lt;p&gt;Now you should be able to open up apps on your device and you will start seeing traffic popup in &lt;strong&gt;mitmweb&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcz1xm70axbdzo5v0v7db.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcz1xm70axbdzo5v0v7db.png" alt="mitmweb screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our example we are capturing the data being sent to &lt;a href="https://thingiverse.com/" rel="noopener noreferrer"&gt;Thingivese&lt;/a&gt; from the mobile app. If you look closely on the right hand side you will find the authentication header. In this header you will find the token being used to validate you as a user. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpndcttpn775zsr4hk90y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fpndcttpn775zsr4hk90y.png" alt="Authentication Header"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the token we can now use to query the API ourselves in our own personal application. &lt;/p&gt;
&lt;h3&gt;
  
  
  Now try query the API yourself.
&lt;/h3&gt;

&lt;p&gt;Here we are pulling the latest listings using Axios and sending the bearer token along with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.thingiverse.com/featured?page=1&amp;amp;per_page=10&amp;amp;return=complete&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5a2d072366d7039776e4c35c5f32efaf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nx"&gt;Axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//the server object listens on port 3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Worth noting
&lt;/h3&gt;

&lt;p&gt;This may will not work for all applications as they may use 'Certificate Pinning' to which you'll need an Android or Jailbroken iOS device. &lt;/p&gt;

&lt;p&gt;You can read more about Certificate Pinning in the docs: &lt;a href="https://docs.mitmproxy.org/stable/concepts-certificates/#certificate-pinning" rel="noopener noreferrer"&gt;Certificate Pinning&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading, for a more in-depth tutorial watch the video above. Follow me &lt;a href="https://twitter.com/sudo_overflow" rel="noopener noreferrer"&gt;@Sudo_Overflow&lt;/a&gt; on Twitter for more.&lt;/p&gt;

&lt;p&gt;Let me know below if you have any other tips or tricks. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>DIY: Generating dynamic images on the fly for Email Marketing</title>
      <dc:creator>Cyris ⭐️ JavaScript</dc:creator>
      <pubDate>Fri, 22 May 2020 06:25:47 +0000</pubDate>
      <link>https://forem.com/sudo_overflow/diy-generating-dynamic-images-on-the-fly-for-email-marketing-h51</link>
      <guid>https://forem.com/sudo_overflow/diy-generating-dynamic-images-on-the-fly-for-email-marketing-h51</guid>
      <description>&lt;p&gt;I was recently tasked to find a lightweight method to generate dynamic images on the fly for Email Campaigns. Sure we could use third-party solutions to do just that but for a fee. These are great services but being a developer I wanted to see if I could build my own that fits my needs.&lt;/p&gt;

&lt;h1&gt;
  
  
  But with a catch
&lt;/h1&gt;

&lt;p&gt;A few rules I had set forward for myself. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It needs to be lightweight&lt;/li&gt;
&lt;li&gt;No headless browsers&lt;/li&gt;
&lt;li&gt;No screenshot tools&lt;/li&gt;
&lt;li&gt;No saving and serving images&lt;/li&gt;
&lt;li&gt;Needs to be fast&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why do we need this?
&lt;/h1&gt;

&lt;p&gt;Email development has come a long way in terms of &lt;strong&gt;what&lt;/strong&gt; is possible but &lt;strong&gt;how&lt;/strong&gt; email is coded still lacks far behind traditional web development. Email still uses &lt;code&gt;&amp;lt;table&amp;gt;&lt;/code&gt; for layout although &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is becoming more popular each day. &lt;/p&gt;

&lt;p&gt;Security concerns prevent us from using scripts such as JavaScript in email, not to mention it would immediately be spammed. People and companies are still using software like Outlook 2010 so cross-platform support for certain elements and layouts is about as messy as those $1 DVD bins. &lt;/p&gt;

&lt;p&gt;In general, Email is largely static, boring, and struggles to capture your target market's attention. So what can we do about this? &lt;/p&gt;

&lt;h1&gt;
  
  
  Introducing dynamically generated imagery
&lt;/h1&gt;

&lt;p&gt;One thing that works on 99% of email clients is images. So we focus our attention on improving that. Using dynamic images allows us to personalize the email to the reader with great custom fonts, designs and even custom animated GIF's to grab the reader's attention. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wYOS1L1---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h7p85dnbj6x655d96202.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wYOS1L1---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/h7p85dnbj6x655d96202.jpg" alt="Personalized email header"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above example is personalizing the header of the email with the recipients first name in a custom font on a background image.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let's get building
&lt;/h1&gt;

&lt;p&gt;In summary we are going to build a simple Express server with NodeJS. This uses the node-canvas module to draw exactly what we want before exporting the canvas as a PNG.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialise a project and install the dependencies
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm init
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;express &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;canvas &lt;span class="nt"&gt;--save&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Create server.js and require the dependencies needed
&lt;/h3&gt;

&lt;p&gt;Don't forget to register your font files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerFont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;createCanvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loadImage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;

&lt;span class="c1"&gt;// We need to register our font file to be used in canvas&lt;/span&gt;
&lt;span class="nx"&gt;registerFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./fonts/Sign-Painter-Regular.ttf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signpainter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Example app listening at http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If all is working you should be able to start your application with &lt;code&gt;node server&lt;/code&gt; and visit your &lt;code&gt;hello world&lt;/code&gt; at &lt;code&gt;http://localhost:3000&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a custom GET route for the image
&lt;/h3&gt;

&lt;p&gt;This should pull in the query parameters to be used in the canvas. In our case, we just want the name so all we have to do is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Grab first name from query&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;decodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Custom canvas added here&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Add your canvas logic inside the route
&lt;/h3&gt;

&lt;p&gt;From our design we know that the only personalization is going to be the first name. So the rest of it can be a "background-image" so to speak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/header&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Grab first name from query&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;firstname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;decodeURI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Define the canvas&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt; &lt;span class="c1"&gt;// width of the image&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;474&lt;/span&gt; &lt;span class="c1"&gt;// height of the image&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Define the font style&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textAlign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textBaseline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#FFFFFF&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;80px 'signpainter' bold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Load and draw the background image first&lt;/span&gt;
    &lt;span class="nx"&gt;loadImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./images/background.jpg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Draw the background&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;474&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Draw the text&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Convert the Canvas to a buffer&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;// Set and send the response as a PNG&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After drawing the background image and text, we are then converting the canvas into a buffer and sending the response back to the client as a PNG image. This allows the client to load the dynamic image on their side.&lt;/p&gt;

&lt;h1&gt;
  
  
  Time to run this.
&lt;/h1&gt;

&lt;p&gt;Start your app with &lt;code&gt;node server&lt;/code&gt; and visit the new route you created at &lt;code&gt;http://localhost:3000/header?name=@Sudo_Overflow&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  And there you have it
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7nTWpuTV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/198cmtuoxtpkodrkfi75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7nTWpuTV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/198cmtuoxtpkodrkfi75.png" alt="Dynamically generated image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now merge the first name into your email's &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag as &lt;br&gt;
&lt;code&gt;&amp;lt;img src="http://localhost:3000/header?name={{FirstName}}"&amp;gt;&lt;/code&gt; and have it automatically generated for you.&lt;/p&gt;




&lt;p&gt;A special thank you to &lt;a href="https://twitter.com/flaviocopes"&gt;@flaviocopes&lt;/a&gt; for the idea of using Canvas. You can check out his article &lt;a href="https://flaviocopes.com/canvas-node-generate-image/"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The full project can be found on my &lt;a href="https://github.com/CyrisXD/dynamic-image-generator"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you know of ways to improve this or have feedback, let me know in the comments or Twitter at &lt;a href="https://twitter.com/sudo_overflow"&gt;@sudo_overflow&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>showdev</category>
      <category>tutorial</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Use Twitter to find developer friends near you</title>
      <dc:creator>Cyris ⭐️ JavaScript</dc:creator>
      <pubDate>Thu, 21 May 2020 05:48:34 +0000</pubDate>
      <link>https://forem.com/sudo_overflow/use-twitter-to-find-developer-friends-near-you-206</link>
      <guid>https://forem.com/sudo_overflow/use-twitter-to-find-developer-friends-near-you-206</guid>
      <description>&lt;p&gt;Being a developer can be tough, especially doing it all alone. That's why I always recommend surrounding yourself with like-minded 'Developer Friends' that support and motivate you. &lt;/p&gt;

&lt;p&gt;Learn from each other, challenge each other, and work together to achieve the goals you set out. You'll not only learn about team dynamics but communication and collaboration. These are all important skills you'll need when working in a team as a developer. &lt;/p&gt;

&lt;p&gt;So if you're looking to connect with like-minded individuals in your area then this trick is for you. &lt;/p&gt;

&lt;h1&gt;
  
  
  I'm intrigued, go on.
&lt;/h1&gt;

&lt;p&gt;Twitter has a really cool feature that not many people know about. You can search for tweets using geolocation data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oTxC1hPn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5pgyrkxqp5rxezj72vc3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oTxC1hPn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5pgyrkxqp5rxezj72vc3.png" alt="Alt Twitter Geolocation Search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A lot of tweets are automatically tagged with geolocation data such a latitude, longitude, and radius (skip ahead if you want to disable this). Using Twitter's own search or API we can query for tweets using these fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The breakdown&lt;/span&gt;
&lt;span class="nx"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;
&lt;span class="c1"&gt;// Actual data&lt;/span&gt;
&lt;span class="nx"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;36.8484597&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;174.7633315&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;km&lt;/span&gt; 
&lt;span class="c1"&gt;// Actual data with search terms included&lt;/span&gt;
&lt;span class="nx"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;40.7127753&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;74.0059728&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="nx"&gt;mi&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Coding&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;OR&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Programming&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Let's give it a try
&lt;/h1&gt;

&lt;p&gt;Here we are searching for anyone tweeting related developer terms within a 5-mile radius of Facebook's Headquarters in California.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qPNLH7bp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gnyq4x0tze22shgwes3x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qPNLH7bp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gnyq4x0tze22shgwes3x.png" alt="Alt Twitter search for developers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Now make it even easier
&lt;/h1&gt;

&lt;p&gt;You're not going to want to keep Googling the coordinates and building your custom search strings each and every time. So I built a quick tool that does it all for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cyris.io/developerfriends/"&gt;Developer Friends&lt;/a&gt; will accept any address, location you can throw at it and it will automatically generate the coordinates and build your custom search query for you. Include the predefined search terms with the tickboxes or even add your own in the text box. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/0UDhO5J4T5Y"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;After hitting search you'll be taken to Twitter with your own custom search query and everyone listed should be within the location and radius you specified. Now go make some developer friends. 👍&lt;/p&gt;

&lt;p&gt;... &lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  I don't want to appear in these searches
&lt;/h1&gt;

&lt;p&gt;No worries, you can disable geotagging on your tweets within Twitter's settings. You can read more about it &lt;a href="https://help.twitter.com/en/safety-and-security/tweet-location-settings"&gt;here&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I hope you'll be able to find, follow, and make plenty of developer friends. If you know any other cool tricks, let me know in the comments or on Twitter &lt;a href="https://twitter.com/sudo_overflow"&gt;@sudo_overflow&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tips</category>
      <category>motivation</category>
      <category>tutorial</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
