<?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>DEV Community: VernonHeim</title>
    <description>The latest articles on DEV Community by VernonHeim (@vernonheim).</description>
    <link>https://dev.to/vernonheim</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4037873%2F5a0fad6d-f0d1-46a2-9350-dba410d5d380.png</url>
      <title>DEV Community: VernonHeim</title>
      <link>https://dev.to/vernonheim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vernonheim"/>
    <language>en</language>
    <item>
      <title>Why Comparing PDFs Is Still Surprisingly Difficult</title>
      <dc:creator>VernonHeim</dc:creator>
      <pubDate>Fri, 04 Sep 2026 01:23:17 +0000</pubDate>
      <link>https://dev.to/vernonheim/why-comparing-pdfs-is-still-surprisingly-difficult-2k</link>
      <guid>https://dev.to/vernonheim/why-comparing-pdfs-is-still-surprisingly-difficult-2k</guid>
      <description>&lt;p&gt;If you've ever had someone send you two PDF files and ask "what changed?", you probably know how this usually goes.&lt;/p&gt;

&lt;p&gt;Open the first file.&lt;/p&gt;

&lt;p&gt;Open the second one.&lt;/p&gt;

&lt;p&gt;Put them next to each other.&lt;/p&gt;

&lt;p&gt;Start scrolling.&lt;/p&gt;

&lt;p&gt;After a few pages, you realize that you're not actually comparing the documents anymore. You're comparing your memory of what you just saw with what you're looking at now.&lt;/p&gt;

&lt;p&gt;It gets worse when the PDFs are nearly identical.&lt;/p&gt;

&lt;p&gt;A 200-page contract with one changed sentence is harder to review than two completely different documents. The less that changed, the easier it is for the important change to disappear into everything that stayed the same.&lt;/p&gt;

&lt;p&gt;This is a problem that seems like it should have been solved a long time ago.&lt;/p&gt;

&lt;p&gt;We have &lt;code&gt;git diff&lt;/code&gt;, IDE diff viewers, image comparison tools, database migration tools, and all kinds of sophisticated ways to answer a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What changed?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PDFs are one of the places where that question is still surprisingly awkward.&lt;/p&gt;

&lt;h2&gt;
  
  
  A PDF isn't really a text document
&lt;/h2&gt;

&lt;p&gt;One reason is that a PDF doesn't behave like a Markdown file, source file, or plain text document.&lt;/p&gt;

&lt;p&gt;If I have two source files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version 1:
const timeout = 30;

version 2:
const timeout = 60;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A normal diff has a very clear job.&lt;/p&gt;

&lt;p&gt;The underlying representation is text, so comparing the two versions is straightforward.&lt;/p&gt;

&lt;p&gt;A PDF is different.&lt;/p&gt;

&lt;p&gt;A page might contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;text&lt;/li&gt;
&lt;li&gt;fonts&lt;/li&gt;
&lt;li&gt;images&lt;/li&gt;
&lt;li&gt;vector graphics&lt;/li&gt;
&lt;li&gt;tables&lt;/li&gt;
&lt;li&gt;headers and footers&lt;/li&gt;
&lt;li&gt;page numbers&lt;/li&gt;
&lt;li&gt;positioned elements&lt;/li&gt;
&lt;li&gt;embedded metadata&lt;/li&gt;
&lt;li&gt;annotations&lt;/li&gt;
&lt;li&gt;different rendering instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The text might be identical while the rendered page is different.&lt;/p&gt;

&lt;p&gt;And the opposite can happen too: two PDFs can look almost identical while containing a meaningful textual change.&lt;/p&gt;

&lt;p&gt;That makes "PDF diff" a slightly different problem from ordinary text diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The obvious solution: extract the text
&lt;/h2&gt;

&lt;p&gt;If you're a developer, one of the first things you might try is extracting the text from both files.&lt;/p&gt;

&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frybfsufqinox7yt21ujx.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frybfsufqinox7yt21ujx.jpg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdftotext old.pdf old.txt
pdftotext new.pdf new.txt

diff &lt;span class="nt"&gt;-u&lt;/span&gt; old.txt new.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is actually a pretty useful solution.&lt;/p&gt;

&lt;p&gt;For simple documents, it may be all you need.&lt;/p&gt;

&lt;p&gt;You can put it into a shell script, run it in CI, or process PDFs with one of the many PDF libraries available for Python, JavaScript, Java, Go, and other languages.&lt;/p&gt;

&lt;p&gt;But there is an annoying limitation.&lt;/p&gt;

&lt;p&gt;You aren't really comparing the PDFs.&lt;/p&gt;

&lt;p&gt;You're comparing the text extracted from the PDFs.&lt;/p&gt;

&lt;p&gt;Those aren't always the same thing.&lt;/p&gt;

&lt;p&gt;Imagine a report where someone changes the position of a table.&lt;/p&gt;

&lt;p&gt;The text extraction could be identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Revenue
2024    $1.2M
2025    $1.8M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the actual PDF might have a completely different layout.&lt;/p&gt;

&lt;p&gt;Or someone replaces a chart with a new image.&lt;/p&gt;

&lt;p&gt;The text diff may report nothing.&lt;/p&gt;

&lt;p&gt;The PDF has clearly changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visual comparison catches a different class of changes
&lt;/h2&gt;

&lt;p&gt;This is where rendering each page to an image and comparing the images becomes useful.&lt;/p&gt;

&lt;p&gt;A simplified workflow might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PDF A
  ↓
Render pages
  ↓
Images A

PDF B
  ↓
Render pages
  ↓
Images B

Images A + Images B
  ↓
Pixel comparison
  ↓
Visual differences
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches things that text extraction misses.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a logo was replaced&lt;/li&gt;
&lt;li&gt;a table moved&lt;/li&gt;
&lt;li&gt;a font changed&lt;/li&gt;
&lt;li&gt;a paragraph wrapped differently&lt;/li&gt;
&lt;li&gt;an image was resized&lt;/li&gt;
&lt;li&gt;a footer moved&lt;/li&gt;
&lt;li&gt;a page break changed&lt;/li&gt;
&lt;li&gt;a signature block disappeared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But visual comparison has its own problems.&lt;/p&gt;

&lt;p&gt;A one-pixel rendering difference can generate a huge number of changed pixels.&lt;/p&gt;

&lt;p&gt;Different PDF rendering engines can produce slightly different output.&lt;/p&gt;

&lt;p&gt;Anti-aliasing can make otherwise identical text appear different at the pixel level.&lt;/p&gt;

&lt;p&gt;So a raw image diff isn't necessarily what a person wants to see.&lt;/p&gt;

&lt;p&gt;The interesting part isn't detecting that 14,381 pixels changed.&lt;/p&gt;

&lt;p&gt;The interesting part is understanding &lt;strong&gt;why they changed&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text diff and visual diff solve different problems
&lt;/h2&gt;

&lt;p&gt;This is why I think PDF comparison is better thought of as two related problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text comparison
&lt;/h3&gt;

&lt;p&gt;It answers questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Was this sentence changed?&lt;/p&gt;

&lt;p&gt;Was a number modified?&lt;/p&gt;

&lt;p&gt;Was a paragraph added or removed?&lt;/p&gt;

&lt;p&gt;Did a clause change from 30 days to 15 days?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Visual comparison
&lt;/h3&gt;

&lt;p&gt;It answers questions such as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the layout change?&lt;/p&gt;

&lt;p&gt;Did an image move?&lt;/p&gt;

&lt;p&gt;Did a table change?&lt;/p&gt;

&lt;p&gt;Did a page break move?&lt;/p&gt;

&lt;p&gt;Did something disappear visually?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Neither approach completely replaces the other.&lt;/p&gt;

&lt;p&gt;For serious document review, having both views is much more useful than choosing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "one tiny change" problem
&lt;/h2&gt;

&lt;p&gt;There is another reason PDF comparison is frustrating.&lt;/p&gt;

&lt;p&gt;Most of the time, people aren't comparing PDFs because the entire document is different.&lt;/p&gt;

&lt;p&gt;They're comparing them because &lt;strong&gt;something small changed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Maybe a lawyer received a revised contract and wants to know what changed since yesterday.&lt;/p&gt;

&lt;p&gt;Maybe a designer exported a new proof and wants to check whether anything moved.&lt;/p&gt;

&lt;p&gt;Maybe a finance team generated this month's report and wants to compare it with last month's version.&lt;/p&gt;

&lt;p&gt;Maybe a developer changed a PDF generation template and needs to verify the output.&lt;/p&gt;

&lt;p&gt;In all of these situations, the important information might represent 0.1% of the document.&lt;/p&gt;

&lt;p&gt;The other 99.9% is noise.&lt;/p&gt;

&lt;p&gt;That's exactly what a useful comparison tool should reduce.&lt;/p&gt;

&lt;p&gt;Instead of asking someone to inspect 100 pages, the goal is to bring attention to the pages and regions that actually changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contracts are a particularly good example
&lt;/h2&gt;

&lt;p&gt;Consider a 70-page agreement.&lt;/p&gt;

&lt;p&gt;Version A says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Payment must be made within 30 days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Version B says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Payment must be made within 15 days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Visually, the two documents may look almost identical.&lt;/p&gt;

&lt;p&gt;The difference is one number.&lt;/p&gt;

&lt;p&gt;But that one number can matter considerably more than everything else in the document.&lt;/p&gt;

&lt;p&gt;A manual side-by-side review has a bad property here: the reviewer has to spend roughly the same amount of attention on unchanged material as changed material.&lt;/p&gt;

&lt;p&gt;A diff reverses that.&lt;/p&gt;

&lt;p&gt;Instead of showing you the entire document and asking you to find the changes, it starts with the changes and lets you inspect the surrounding context.&lt;/p&gt;

&lt;p&gt;That's the basic idea behind almost every useful diff tool, and PDFs shouldn't be an exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generated PDFs create another interesting problem
&lt;/h2&gt;

&lt;p&gt;PDF comparison isn't only useful for lawyers and document reviewers.&lt;/p&gt;

&lt;p&gt;It's also useful for developers who generate PDFs automatically.&lt;/p&gt;

&lt;p&gt;Suppose your application generates invoices.&lt;/p&gt;

&lt;p&gt;You change the HTML template.&lt;/p&gt;

&lt;p&gt;The code compiles.&lt;/p&gt;

&lt;p&gt;The tests pass.&lt;/p&gt;

&lt;p&gt;The invoice is generated successfully.&lt;/p&gt;

&lt;p&gt;Nothing crashes.&lt;/p&gt;

&lt;p&gt;But now the total is pushed onto another line, a table is slightly wider, or a footer overlaps some content.&lt;/p&gt;

&lt;p&gt;A normal unit test may not catch that.&lt;/p&gt;

&lt;p&gt;This is where PDF comparison can become part of a testing workflow.&lt;/p&gt;

&lt;p&gt;You can generate a known document, compare it against an expected version, and investigate unexpected changes.&lt;/p&gt;

&lt;p&gt;This is similar to visual regression testing for websites.&lt;/p&gt;

&lt;p&gt;The difference is that the output is a PDF rather than a browser screenshot.&lt;/p&gt;

&lt;p&gt;For document-heavy applications, that can be surprisingly useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  But automation isn't always the answer
&lt;/h2&gt;

&lt;p&gt;It's tempting to turn everything into a pipeline.&lt;/p&gt;

&lt;p&gt;Sometimes that's exactly right.&lt;/p&gt;

&lt;p&gt;If you're generating thousands of PDFs, you probably don't want a human opening them one by one.&lt;/p&gt;

&lt;p&gt;But if you're comparing two documents once, building a custom PDF processing pipeline can be overkill.&lt;/p&gt;

&lt;p&gt;There's a large gap between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I need to compare these two files right now."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I need an automated document-diff system integrated into our CI infrastructure."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first problem benefits from convenience.&lt;/p&gt;

&lt;p&gt;The second benefits from APIs, scripting, reproducibility, and automation.&lt;/p&gt;

&lt;p&gt;A good workflow should recognize the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy matters more than it first appears
&lt;/h2&gt;

&lt;p&gt;There's also a less obvious issue with PDF comparison.&lt;/p&gt;

&lt;p&gt;Documents often contain information that shouldn't be casually uploaded somewhere.&lt;/p&gt;

&lt;p&gt;Think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;employment contracts&lt;/li&gt;
&lt;li&gt;customer invoices&lt;/li&gt;
&lt;li&gt;financial statements&lt;/li&gt;
&lt;li&gt;internal reports&lt;/li&gt;
&lt;li&gt;legal agreements&lt;/li&gt;
&lt;li&gt;product specifications&lt;/li&gt;
&lt;li&gt;unreleased documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're comparing those files, where the comparison happens matters.&lt;/p&gt;

&lt;p&gt;There is a big difference between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  ↓
Process document locally
  ↓
Show result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  ↓
Upload document
  ↓
Remote server
  ↓
Process document
  ↓
Return result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither architecture is automatically right or wrong for every situation.&lt;/p&gt;

&lt;p&gt;Server-side processing can make certain features easier.&lt;/p&gt;

&lt;p&gt;Client-side processing can reduce the need to send sensitive documents to a server.&lt;/p&gt;

&lt;p&gt;The important thing is that users should know which model they're using.&lt;/p&gt;

&lt;p&gt;For document tools, privacy isn't just a checkbox in a settings page. It's part of the workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built PdfCompare
&lt;/h2&gt;

&lt;p&gt;This was one of the reasons I started working on &lt;strong&gt;PdfCompare&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The basic idea is pretty simple:&lt;/p&gt;

&lt;p&gt;I didn't want PDF comparison to require a large desktop application for every small task.&lt;/p&gt;

&lt;p&gt;If someone has two PDFs and wants to know what changed, there should be a straightforward way to do that from a browser.&lt;/p&gt;

&lt;p&gt;PdfCompare focuses on comparing PDFs directly in the browser, with both textual and visual differences available for inspection.&lt;/p&gt;

&lt;p&gt;The browser-based approach is particularly useful for quick comparisons where installing software, creating an account, or setting up a document-processing workflow feels like more work than the actual task.&lt;/p&gt;

&lt;p&gt;You can use a &lt;a href="https://pdfcompare.app/" rel="noopener noreferrer"&gt;pdf compare tool&lt;/a&gt; when you simply need to answer the question "what changed between these two files?" without turning the task into a larger software project.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I think a useful PDF diff should show
&lt;/h2&gt;

&lt;p&gt;After working through the problem, I think there are a few things that make a PDF comparison workflow genuinely useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Don't hide the original documents
&lt;/h3&gt;

&lt;p&gt;The comparison is easier to understand when you can still inspect the original pages.&lt;/p&gt;

&lt;p&gt;A diff shouldn't become another opaque representation that requires interpretation.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Make small changes obvious
&lt;/h3&gt;

&lt;p&gt;If one word changed in a paragraph, the user shouldn't have to scan the entire page to find it.&lt;/p&gt;

&lt;p&gt;The changed region should stand out.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Show context
&lt;/h3&gt;

&lt;p&gt;Highlighting a changed word is useful.&lt;/p&gt;

&lt;p&gt;Showing that word in the context of the surrounding paragraph is better.&lt;/p&gt;

&lt;p&gt;The user needs enough of the original document to understand what the change means.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Handle visual changes separately
&lt;/h3&gt;

&lt;p&gt;A changed image isn't necessarily a text change.&lt;/p&gt;

&lt;p&gt;A moved table isn't necessarily a text change.&lt;/p&gt;

&lt;p&gt;A visual comparison mode makes those changes much easier to inspect.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Don't make users install something for every small job
&lt;/h3&gt;

&lt;p&gt;Desktop applications still have their place.&lt;/p&gt;

&lt;p&gt;So do command-line tools and libraries.&lt;/p&gt;

&lt;p&gt;But there should also be a low-friction option for someone who just received:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract-final.pdf
contract-final-2.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and wants to know what happened between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where command-line tools still win
&lt;/h2&gt;

&lt;p&gt;None of this means browser tools replace developer tooling.&lt;/p&gt;

&lt;p&gt;If I'm building an automated system, I'd rather have a scriptable library than manually upload documents somewhere.&lt;/p&gt;

&lt;p&gt;For example, a development team might want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generate PDF
      ↓
Compare against baseline
      ↓
Detect unexpected changes
      ↓
Fail CI if necessary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a completely different requirement.&lt;/p&gt;

&lt;p&gt;Tools such as &lt;code&gt;pdftotext&lt;/code&gt;, PDF parsing libraries, rendering libraries, ImageMagick, and custom comparison algorithms can be combined to build sophisticated workflows.&lt;/p&gt;

&lt;p&gt;The advantage is control.&lt;/p&gt;

&lt;p&gt;The downside is engineering time.&lt;/p&gt;

&lt;p&gt;There isn't one perfect PDF comparison workflow.&lt;/p&gt;

&lt;p&gt;There are just different workflows for different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that still feels strange
&lt;/h2&gt;

&lt;p&gt;What's surprising to me is how mature document formats and developer tooling have become, yet this particular problem remains so common.&lt;/p&gt;

&lt;p&gt;We routinely compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Git commits
JSON files
CSV files
source code
images
database schemas
configuration files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But when the output becomes a PDF, the workflow often goes back to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Open both files and look carefully.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;PDFs are everywhere in business.&lt;/p&gt;

&lt;p&gt;They're used for contracts, invoices, reports, specifications, applications, manuals, academic papers, and exported application data.&lt;/p&gt;

&lt;p&gt;So "what changed?" is a question worth answering well.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical rule of thumb
&lt;/h2&gt;

&lt;p&gt;If you're comparing source documents during development, use a text or structured diff whenever possible.&lt;/p&gt;

&lt;p&gt;If you're validating generated PDFs, consider combining text comparison with visual regression testing.&lt;/p&gt;

&lt;p&gt;If you're reviewing contracts or business documents, you probably want a human-readable diff that makes meaningful changes easy to inspect.&lt;/p&gt;

&lt;p&gt;And if you simply have two PDFs sitting on your desktop and need to know what changed, use the simplest workflow that gets you the answer.&lt;/p&gt;

&lt;p&gt;That's ultimately what PdfCompare is trying to solve.&lt;/p&gt;

&lt;p&gt;Not "PDFs are complicated."&lt;/p&gt;

&lt;p&gt;Not "we need another PDF editor."&lt;/p&gt;

&lt;p&gt;Just this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You have two versions of a document. You want to see what changed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That shouldn't require manually reading both documents from beginning to end.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Peer-to-Peer Clipboard Sync System with WebRTC</title>
      <dc:creator>VernonHeim</dc:creator>
      <pubDate>Mon, 10 Aug 2026 02:32:22 +0000</pubDate>
      <link>https://dev.to/vernonheim/building-a-peer-to-peer-clipboard-sync-system-with-webrtc-1b2n</link>
      <guid>https://dev.to/vernonheim/building-a-peer-to-peer-clipboard-sync-system-with-webrtc-1b2n</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5pc4sibdtookcqf1wwfp.webp" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5pc4sibdtookcqf1wwfp.webp" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;br&gt;
A few months ago, I noticed a small but annoying workflow problem.&lt;/p&gt;

&lt;p&gt;I was constantly moving small pieces of information between devices.&lt;/p&gt;

&lt;p&gt;A code snippet from my laptop to my phone.&lt;/p&gt;

&lt;p&gt;A URL from one browser to another.&lt;/p&gt;

&lt;p&gt;A temporary note that I needed somewhere else.&lt;/p&gt;

&lt;p&gt;The amount of data was never the problem. The problem was the unnecessary friction.&lt;/p&gt;

&lt;p&gt;Most clipboard synchronization tools solve this problem by introducing a cloud service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Device A
    |
    |
    v
Cloud Server
    |
    |
    v
Device B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is simple and reliable, but it also means clipboard content leaves your devices.&lt;/p&gt;

&lt;p&gt;For many cases, that may be acceptable. But clipboard data can contain sensitive information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passwords&lt;/li&gt;
&lt;li&gt;Authentication codes&lt;/li&gt;
&lt;li&gt;Private messages&lt;/li&gt;
&lt;li&gt;Source code&lt;/li&gt;
&lt;li&gt;Temporary documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clipboard content is usually short-lived. It exists for seconds or minutes and then disappears.&lt;/p&gt;

&lt;p&gt;This raised an interesting question:&lt;/p&gt;

&lt;p&gt;Can we synchronize clipboard content directly between devices without sending it to a server?&lt;/p&gt;

&lt;p&gt;The answer is yes, using WebRTC DataChannels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The basic idea is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Signaling Server

          Exchange connection data

Device A  -------------------- Device B

             WebRTC DataChannel

          Direct peer-to-peer data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signaling server is only responsible for helping devices discover each other.&lt;/p&gt;

&lt;p&gt;It exchanges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Session descriptions&lt;/li&gt;
&lt;li&gt;ICE candidates&lt;/li&gt;
&lt;li&gt;Connection information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the connection is established, clipboard data travels directly between peers.&lt;/p&gt;

&lt;p&gt;The data flow changes from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy
 |
Upload
 |
Store on server
 |
Download
 |
Paste
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Copy
 |
Create message
 |
WebRTC DataChannel
 |
Receive
 |
Update clipboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why WebRTC DataChannel?
&lt;/h2&gt;

&lt;p&gt;WebRTC is mostly known for video calls and real-time communication.&lt;/p&gt;

&lt;p&gt;However, the most interesting part for this use case is DataChannel.&lt;/p&gt;

&lt;p&gt;DataChannel allows browsers to exchange arbitrary data directly between peers.&lt;/p&gt;

&lt;p&gt;It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Low latency communication&lt;/li&gt;
&lt;li&gt;Peer-to-peer transport&lt;/li&gt;
&lt;li&gt;Encrypted communication&lt;/li&gt;
&lt;li&gt;Support for text and binary data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clipboard synchronization does not require huge bandwidth.&lt;/p&gt;

&lt;p&gt;A clipboard update is usually just a small message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clipboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello from another device"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1720000000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no reason to upload this tiny piece of data to a central storage system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting clipboard changes
&lt;/h2&gt;

&lt;p&gt;The first challenge is clipboard monitoring.&lt;/p&gt;

&lt;p&gt;Modern browsers provide the Clipboard API:&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, clipboard access is intentionally restricted.&lt;/p&gt;

&lt;p&gt;Browsers do this because unrestricted clipboard access would create serious security problems.&lt;/p&gt;

&lt;p&gt;A malicious website could silently read:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Passwords copied from password managers&lt;/li&gt;
&lt;li&gt;One-time authentication codes&lt;/li&gt;
&lt;li&gt;Private conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, applications need proper permissions and user interaction.&lt;/p&gt;

&lt;p&gt;A simplified clipboard reader:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getClipboardContent&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;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;content&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;Once new content is detected, it can be sent through the WebRTC connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sending clipboard data through WebRTC
&lt;/h2&gt;

&lt;p&gt;Creating a DataChannel is straightforward:&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;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nx"&gt;peerConnection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDataChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onopen&lt;/span&gt; &lt;span class="o"&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="s2"&gt;Connected&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;Sending clipboard content:&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="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&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;text&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;Receiving data:&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="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&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="nx"&gt;message&lt;/span&gt; &lt;span class="o"&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;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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;The browser does not need to know the physical location of the other device.&lt;/p&gt;

&lt;p&gt;WebRTC handles the communication layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The difficult part: establishing the connection
&lt;/h2&gt;

&lt;p&gt;The DataChannel itself is simple.&lt;/p&gt;

&lt;p&gt;The complicated part is creating the connection.&lt;/p&gt;

&lt;p&gt;WebRTC requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SDP negotiation&lt;/li&gt;
&lt;li&gt;ICE candidate exchange&lt;/li&gt;
&lt;li&gt;NAT traversal&lt;/li&gt;
&lt;li&gt;STUN/TURN infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connection process looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Device A

Create Offer

      |
      v

Signaling Server

      |
      v

Device B

Create Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After both devices exchange connection information, WebRTC attempts to create the best possible network path.&lt;/p&gt;

&lt;p&gt;In many home networks, devices can communicate directly.&lt;/p&gt;

&lt;p&gt;However, some environments are more complicated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Corporate networks&lt;/li&gt;
&lt;li&gt;Strict NAT&lt;/li&gt;
&lt;li&gt;Mobile carrier networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those situations, TURN servers may be required as a relay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing clipboard synchronization loops
&lt;/h2&gt;

&lt;p&gt;A real implementation quickly runs into another problem.&lt;/p&gt;

&lt;p&gt;Imagine two devices:&lt;/p&gt;

&lt;p&gt;Device A copies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content is sent to Device B.&lt;/p&gt;

&lt;p&gt;Device B updates its clipboard.&lt;/p&gt;

&lt;p&gt;The clipboard watcher on Device B detects the change.&lt;/p&gt;

&lt;p&gt;Now Device B sends the same content back to Device A.&lt;/p&gt;

&lt;p&gt;Without protection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Device A
    |
    v
Device B
    |
    v
Device A
    |
    v
Device B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prevent this, each clipboard message needs metadata.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-message-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"device-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clipboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Devices can keep a small history of processed message IDs.&lt;/p&gt;

&lt;p&gt;If the same message appears again, it is ignored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security considerations
&lt;/h2&gt;

&lt;p&gt;Clipboard synchronization requires careful handling.&lt;/p&gt;

&lt;p&gt;A good design should consider:&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid permanent storage
&lt;/h3&gt;

&lt;p&gt;Clipboard data should only exist during transmission.&lt;/p&gt;

&lt;p&gt;Saving clipboard history introduces unnecessary risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encrypt communication
&lt;/h3&gt;

&lt;p&gt;WebRTC provides encrypted communication channels.&lt;/p&gt;

&lt;p&gt;The goal is that clipboard content should only be readable by the connected devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify connected devices
&lt;/h3&gt;

&lt;p&gt;A user should always know which devices are paired.&lt;/p&gt;

&lt;p&gt;A clipboard synchronization tool should never silently connect unknown devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not use a traditional backend?
&lt;/h2&gt;

&lt;p&gt;A backend API would be easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /clipboard

GET /clipboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design is simple.&lt;/p&gt;

&lt;p&gt;But it creates a central point that receives clipboard data.&lt;/p&gt;

&lt;p&gt;The trade-off looks like this:&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud based approach
&lt;/h3&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier implementation&lt;/li&gt;
&lt;li&gt;Works across networks&lt;/li&gt;
&lt;li&gt;Simple device history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server receives clipboard data&lt;/li&gt;
&lt;li&gt;Requires storage handling&lt;/li&gt;
&lt;li&gt;Privacy concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Peer-to-peer approach
&lt;/h3&gt;

&lt;p&gt;Advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data stays between devices&lt;/li&gt;
&lt;li&gt;No temporary cloud storage&lt;/li&gt;
&lt;li&gt;Lower latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More networking complexity&lt;/li&gt;
&lt;li&gt;Requires WebRTC connection management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For temporary personal data, peer-to-peer communication is an interesting alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a real implementation
&lt;/h2&gt;

&lt;p&gt;While exploring this architecture, I built Textunnel, a browser-based tool for moving text, code, and files between devices.&lt;/p&gt;

&lt;p&gt;The clipboard synchronization feature follows the same idea: keep your data moving directly between your own devices.&lt;/p&gt;

&lt;p&gt;You can try it here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://textunnel.com/cross-device-clipboard" rel="noopener noreferrer"&gt;clipboard sync&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Clipboard synchronization looks like a small feature, but building it properly involves many interesting engineering problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browser security restrictions&lt;/li&gt;
&lt;li&gt;Real-time communication&lt;/li&gt;
&lt;li&gt;WebRTC networking&lt;/li&gt;
&lt;li&gt;NAT traversal&lt;/li&gt;
&lt;li&gt;Peer-to-peer architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebRTC is often introduced as a technology for video calls.&lt;/p&gt;

&lt;p&gt;But DataChannels make it possible to build many other types of applications.&lt;/p&gt;

&lt;p&gt;Sometimes the best way to move your data is not through a server.&lt;/p&gt;

&lt;p&gt;Sometimes it is directly between the devices that already belong to you.&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>privacy</category>
    </item>
    <item>
      <title>How WebRTC Enables Fast and Private Peer-to-Peer File Sharing</title>
      <dc:creator>VernonHeim</dc:creator>
      <pubDate>Mon, 20 Jul 2026 14:00:28 +0000</pubDate>
      <link>https://dev.to/vernonheim/how-webrtc-enables-fast-and-private-peer-to-peer-file-sharing-50g0</link>
      <guid>https://dev.to/vernonheim/how-webrtc-enables-fast-and-private-peer-to-peer-file-sharing-50g0</guid>
      <description>&lt;p&gt;Sharing content between devices sounds like a simple problem. &lt;br&gt;
I explored this everyday frustration in more detail in &lt;a href="https://medium.com/@heyuyang1994/why-moving-files-between-my-own-devices-is-still-more-complicated-than-it-should-be-8da875a4a278" rel="noopener noreferrer"&gt;Why Moving Files Between My Own Devices Is Still More Complicated Than It Should Be&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You copy some text on your laptop, open your phone, and realize there is no easy way to move that information. Maybe it is a code snippet, a temporary file, a URL, or a piece of text you need immediately.&lt;/p&gt;

&lt;p&gt;Traditional solutions usually rely on cloud storage:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload data to a server&lt;/li&gt;
&lt;li&gt;Store it temporarily&lt;/li&gt;
&lt;li&gt;Download it on another device&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This works, but it introduces additional complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your data needs to travel through third-party servers&lt;/li&gt;
&lt;li&gt;Temporary information may be stored longer than expected&lt;/li&gt;
&lt;li&gt;Users need accounts and complicated workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebRTC provides another approach: direct peer-to-peer communication.&lt;/p&gt;
&lt;h2&gt;
  
  
  What is WebRTC?
&lt;/h2&gt;

&lt;p&gt;WebRTC (Web Real-Time Communication) is an open technology that enables browsers and applications to exchange data directly between devices.&lt;/p&gt;

&lt;p&gt;Originally designed for real-time communication such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Video calls&lt;/li&gt;
&lt;li&gt;Audio communication&lt;/li&gt;
&lt;li&gt;Screen sharing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebRTC also supports direct data transfer through the RTCDataChannel API.&lt;/p&gt;

&lt;p&gt;This makes it possible to build applications where devices can communicate without sending every piece of data through a centralized server.&lt;/p&gt;
&lt;h2&gt;
  
  
  How Peer-to-Peer Data Transfer Works
&lt;/h2&gt;

&lt;p&gt;A simplified WebRTC data transfer flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Device A
   |
   | 1. Create connection request
   |
Signaling Server
   |
   | 2. Exchange connection information
   |
Device B
   |
   | 3. Establish direct P2P connection
   |
Encrypted Data Channel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signaling server is only responsible for helping devices discover each other.&lt;/p&gt;

&lt;p&gt;After the connection is established, the actual data can flow directly between peers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Peer-to-Peer Sharing Matters
&lt;/h2&gt;

&lt;p&gt;For many everyday tasks, users do not need a full cloud storage system.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending a code snippet from a desktop to a laptop&lt;/li&gt;
&lt;li&gt;Moving a URL from a phone to a computer&lt;/li&gt;
&lt;li&gt;Sharing a small document during development&lt;/li&gt;
&lt;li&gt;Transferring temporary files between personal devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these situations, speed and privacy are often more important than long-term storage.&lt;/p&gt;

&lt;p&gt;A peer-to-peer approach provides several advantages.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Faster Transfers
&lt;/h3&gt;

&lt;p&gt;When devices communicate directly, data does not need to travel through a large storage infrastructure.&lt;/p&gt;

&lt;p&gt;This can reduce latency, especially for users on the same network.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Better Privacy
&lt;/h3&gt;

&lt;p&gt;With traditional file sharing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User → Cloud Server → User Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server becomes part of the data path.&lt;/p&gt;

&lt;p&gt;With peer-to-peer communication:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Device → User Device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The communication is more direct.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. No Account Required
&lt;/h3&gt;

&lt;p&gt;Many temporary sharing scenarios do not need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User accounts&lt;/li&gt;
&lt;li&gt;Password management&lt;/li&gt;
&lt;li&gt;Permanent storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lightweight connection can solve the problem immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges of Building WebRTC Applications
&lt;/h2&gt;

&lt;p&gt;Although WebRTC is powerful, building a reliable application requires solving several problems.&lt;/p&gt;

&lt;h3&gt;
  
  
  NAT Traversal
&lt;/h3&gt;

&lt;p&gt;Most devices are behind routers or firewalls.&lt;/p&gt;

&lt;p&gt;To establish connections, WebRTC uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ICE (Interactive Connectivity Establishment)&lt;/li&gt;
&lt;li&gt;STUN servers&lt;/li&gt;
&lt;li&gt;TURN servers when direct connections fail&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to find the best possible communication path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Management
&lt;/h3&gt;

&lt;p&gt;Real-world networks are unpredictable.&lt;/p&gt;

&lt;p&gt;Applications need to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connection failures&lt;/li&gt;
&lt;li&gt;Device switching&lt;/li&gt;
&lt;li&gt;Network changes&lt;/li&gt;
&lt;li&gt;Browser compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  User Experience
&lt;/h3&gt;

&lt;p&gt;A technically correct WebRTC implementation can still fail if the user experience is complicated.&lt;/p&gt;

&lt;p&gt;Users usually want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open&lt;/li&gt;
&lt;li&gt;Connect&lt;/li&gt;
&lt;li&gt;Share&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an account&lt;/li&gt;
&lt;li&gt;Configure settings&lt;/li&gt;
&lt;li&gt;Upload files&lt;/li&gt;
&lt;li&gt;Manage storage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building Simpler Cross-Device Experiences
&lt;/h2&gt;

&lt;p&gt;Modern users often work across multiple devices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop computers&lt;/li&gt;
&lt;li&gt;Laptops&lt;/li&gt;
&lt;li&gt;Phones&lt;/li&gt;
&lt;li&gt;Tablets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The problem is not always file storage.&lt;/p&gt;

&lt;p&gt;Sometimes users simply need a temporary bridge between their devices.&lt;/p&gt;

&lt;p&gt;This idea inspired us to build Textunnel, a privacy-focused cross-device sharing tool designed for quickly moving text, code, links, and files between devices.&lt;/p&gt;

&lt;p&gt;We also shared the story behind this decision, including why we chose a peer-to-peer approach instead of building another traditional cloud storage service in &lt;a href="https://textunnel.hashnode.dev/why-we-built-a-peer-to-peer-file-sharing-tool-instead-of-another-cloud-storage-app" rel="noopener noreferrer"&gt;Why We Built a Peer-to-Peer File Sharing Tool Instead of Another Cloud Storage App&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead of creating another cloud drive, the goal is to make device-to-device communication simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taking WebRTC Beyond Theory
&lt;/h2&gt;

&lt;p&gt;I didn't just want to understand WebRTC — I wanted to build something people &lt;br&gt;
could actually use. So I created &lt;strong&gt;&lt;a href="https://textunnel.com/" rel="noopener noreferrer"&gt;Textunnel&lt;/a&gt;&lt;/strong&gt;, a &lt;br&gt;
browser-based tool for peer-to-peer file and text sharing. &lt;/p&gt;

&lt;p&gt;A few of the specific tools I've built on top of WebRTC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://textunnel.com/qr-file-transfer-without-internet" rel="noopener noreferrer"&gt;QR File Transfer Without Internet&lt;/a&gt;&lt;/strong&gt; 
uses QR codes for signalling, so you can transfer files without any internet 
connection at all&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://textunnel.com/bluetooth-webrtc-file-transfer-without-wifi" rel="noopener noreferrer"&gt;Bluetooth WebRTC File Transfer Without WiFi&lt;/a&gt;&lt;/strong&gt;
combines Bluetooth discovery with WebRTC data channels for local transfers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://textunnel.com/share-clipboard-text-across-different-operating-systems" rel="noopener noreferrer"&gt;Share Clipboard Text Across Operating Systems&lt;/a&gt;&lt;/strong&gt;
lets you copy text on one device and paste it on another — regardless of OS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these tools uses the same WebRTC fundamentals we covered above, just &lt;br&gt;
optimized for a specific use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Peer-to-Peer Applications
&lt;/h2&gt;

&lt;p&gt;As browsers become more capable, peer-to-peer technologies will continue to create new possibilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decentralized collaboration tools&lt;/li&gt;
&lt;li&gt;Private communication platforms&lt;/li&gt;
&lt;li&gt;Local-first applications&lt;/li&gt;
&lt;li&gt;Real-time developer workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebRTC is not only a technology for video calls. It is a foundation for building faster, more private, and more user-controlled applications.&lt;/p&gt;

&lt;p&gt;For developers building the next generation of productivity tools, peer-to-peer communication is worth exploring.&lt;br&gt;
This idea inspired us to build &lt;a href="https://textunnel.com/" rel="noopener noreferrer"&gt;Textunnel&lt;/a&gt;...&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>security</category>
      <category>opensource</category>
      <category>webrtc</category>
    </item>
  </channel>
</rss>
