<?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: Sora Labs</title>
    <description>The latest articles on DEV Community by Sora Labs (@soralabs).</description>
    <link>https://dev.to/soralabs</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%2F4094556%2F68bebdf2-4dcc-4555-82eb-06a80ec3d6be.png</url>
      <title>DEV Community: Sora Labs</title>
      <link>https://dev.to/soralabs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/soralabs"/>
    <language>en</language>
    <item>
      <title>7 Engineering Lessons From Building File Processing Directly in the Browser</title>
      <dc:creator>Sora Labs</dc:creator>
      <pubDate>Tue, 25 Aug 2026 17:31:57 +0000</pubDate>
      <link>https://dev.to/soralabs/7-engineering-lessons-from-building-file-processing-directly-in-the-browser-571d</link>
      <guid>https://dev.to/soralabs/7-engineering-lessons-from-building-file-processing-directly-in-the-browser-571d</guid>
      <description>&lt;p&gt;I've been building SoraFiles, a privacy-first web app for working with PDFs and images directly in the browser.&lt;/p&gt;

&lt;p&gt;The interesting part has not been creating another upload form.&lt;/p&gt;

&lt;p&gt;It has been removing the upload step entirely for supported workflows.&lt;/p&gt;

&lt;p&gt;The basic architecture is:&lt;/p&gt;

&lt;p&gt;User file&lt;br&gt;
-&amp;gt; Browser File API&lt;br&gt;
-&amp;gt; Local JavaScript / WebAssembly processing&lt;br&gt;
-&amp;gt; Result&lt;br&gt;
-&amp;gt; Download&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;p&gt;Browser&lt;br&gt;
-&amp;gt; Upload&lt;br&gt;
-&amp;gt; Processing server&lt;br&gt;
-&amp;gt; Temporary storage&lt;br&gt;
-&amp;gt; Download&lt;/p&gt;

&lt;p&gt;That sounds simple until you try to make it reliable on phones, Safari, large PDFs, corrupt files, repeated user actions, and limited browser memory.&lt;/p&gt;

&lt;p&gt;Here are seven engineering lessons that have mattered most.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  1. TREAT EVERY PROCESSING OPERATION AS A JOB
&lt;/h1&gt;

&lt;p&gt;The simplest implementation is also the one most likely to create stale-state bugs.&lt;/p&gt;

&lt;p&gt;A user clicks Process.&lt;/p&gt;

&lt;p&gt;The job starts.&lt;/p&gt;

&lt;p&gt;Before it finishes, they remove the file and select another one.&lt;/p&gt;

&lt;p&gt;If the first operation is still allowed to update the interface, the old result can suddenly appear inside the new workflow.&lt;/p&gt;

&lt;p&gt;That is confusing and dangerous.&lt;/p&gt;

&lt;p&gt;A better model is to treat every processing operation as a job with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a unique identity&lt;/li&gt;
&lt;li&gt;a state&lt;/li&gt;
&lt;li&gt;cancellation&lt;/li&gt;
&lt;li&gt;ownership of its resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lifecycle can be thought of as:&lt;/p&gt;

&lt;p&gt;Idle&lt;br&gt;
-&amp;gt; Ready&lt;br&gt;
-&amp;gt; Processing&lt;br&gt;
-&amp;gt; Success / Error / Cancelled&lt;/p&gt;

&lt;p&gt;Before any asynchronous task updates the UI, the application should verify that it still belongs to the current active job.&lt;/p&gt;

&lt;p&gt;That one idea prevents a surprising number of bugs.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  2. WEB WORKERS HELP, BUT OWNERSHIP STILL MATTERS
&lt;/h1&gt;

&lt;p&gt;PDF rendering, OCR, compression, and image conversion can become expensive quickly.&lt;/p&gt;

&lt;p&gt;Moving heavy work into Web Workers helps keep the interface responsive.&lt;/p&gt;

&lt;p&gt;But a Worker does not magically solve memory problems.&lt;/p&gt;

&lt;p&gt;Large buffers may still need to move between the main thread and the Worker.&lt;/p&gt;

&lt;p&gt;So the important questions become:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who owns this buffer?&lt;/li&gt;
&lt;li&gt;Was it copied or transferred?&lt;/li&gt;
&lt;li&gt;Can the main thread still use it?&lt;/li&gt;
&lt;li&gt;What happens when the user cancels?&lt;/li&gt;
&lt;li&gt;Who cleans it up?&lt;/li&gt;
&lt;li&gt;Can the Worker be terminated safely?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local processing becomes much easier to reason about when data ownership is explicit.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  3. CLEANUP IS PART OF THE ALGORITHM
&lt;/h1&gt;

&lt;p&gt;A browser-local file application can create a lot of temporary resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;canvases&lt;/li&gt;
&lt;li&gt;image bitmaps&lt;/li&gt;
&lt;li&gt;PDF rendering tasks&lt;/li&gt;
&lt;li&gt;object URLs&lt;/li&gt;
&lt;li&gt;Blobs&lt;/li&gt;
&lt;li&gt;ArrayBuffers&lt;/li&gt;
&lt;li&gt;workers&lt;/li&gt;
&lt;li&gt;event listeners&lt;/li&gt;
&lt;li&gt;temporary decoded documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you create them and never clean them up, the browser may keep enough memory alive to make the app unstable on mobile.&lt;/p&gt;

&lt;p&gt;I now think of cleanup as part of the actual processing pipeline:&lt;/p&gt;

&lt;p&gt;Validate&lt;br&gt;
-&amp;gt; Decode&lt;br&gt;
-&amp;gt; Process&lt;br&gt;
-&amp;gt; Encode&lt;br&gt;
-&amp;gt; Expose result&lt;br&gt;
-&amp;gt; Cleanup&lt;/p&gt;

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

&lt;p&gt;Process&lt;br&gt;
-&amp;gt; Hope garbage collection eventually fixes everything&lt;/p&gt;

&lt;p&gt;This matters especially on iPhones and lower-memory devices.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  4. A 20 MB FILE DOES NOT MEAN 20 MB OF MEMORY
&lt;/h1&gt;

&lt;p&gt;Compressed file size and in-memory size are very different things.&lt;/p&gt;

&lt;p&gt;A 20 MB PDF may temporarily involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original ArrayBuffer&lt;/li&gt;
&lt;li&gt;decoded page data&lt;/li&gt;
&lt;li&gt;several canvases&lt;/li&gt;
&lt;li&gt;preview images&lt;/li&gt;
&lt;li&gt;intermediate output&lt;/li&gt;
&lt;li&gt;the final result Blob&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Images can expand dramatically when decoded into raw pixels.&lt;/p&gt;

&lt;p&gt;So large-file support is not just about removing an arbitrary upload limit.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;bounded concurrency&lt;/li&gt;
&lt;li&gt;sequential processing where possible&lt;/li&gt;
&lt;li&gt;avoiding unnecessary buffer copies&lt;/li&gt;
&lt;li&gt;releasing resources quickly&lt;/li&gt;
&lt;li&gt;lower-resolution previews&lt;/li&gt;
&lt;li&gt;cancellation&lt;/li&gt;
&lt;li&gt;capability-aware limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Desktop browsers can hide inefficient architecture.&lt;/p&gt;

&lt;p&gt;Phones expose it.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  5. PREVIEW RENDERING CAN BECOME A PERFORMANCE BUG
&lt;/h1&gt;

&lt;p&gt;A preview looks like a UI problem.&lt;/p&gt;

&lt;p&gt;It is also a memory problem.&lt;/p&gt;

&lt;p&gt;Suppose a PDF page is displayed inside a 320-pixel-wide card.&lt;/p&gt;

&lt;p&gt;If you render that page internally at several thousand pixels and simply shrink it with CSS, the preview may look correct while still consuming far more memory than necessary.&lt;/p&gt;

&lt;p&gt;A better approach is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Determine the actual visible preview size.&lt;/li&gt;
&lt;li&gt;Account for device pixel ratio.&lt;/li&gt;
&lt;li&gt;Render close to the resolution the UI genuinely needs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The browser is both your renderer and your processing machine.&lt;/p&gt;

&lt;p&gt;Waste resources on the preview and you leave fewer resources for the actual file operation.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  6. DEFINE WHAT ANALYTICS MUST NEVER RECEIVE
&lt;/h1&gt;

&lt;p&gt;Analytics usually starts with:&lt;/p&gt;

&lt;p&gt;"What should we track?"&lt;/p&gt;

&lt;p&gt;For software handling private documents, I think the first question should be:&lt;/p&gt;

&lt;p&gt;"What should never reach analytics?"&lt;/p&gt;

&lt;p&gt;For SoraFiles, file-derived information should remain outside the analytics layer.&lt;/p&gt;

&lt;p&gt;That includes things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;filenames&lt;/li&gt;
&lt;li&gt;document text&lt;/li&gt;
&lt;li&gt;OCR output&lt;/li&gt;
&lt;li&gt;metadata values&lt;/li&gt;
&lt;li&gt;passwords&lt;/li&gt;
&lt;li&gt;signatures&lt;/li&gt;
&lt;li&gt;image pixels&lt;/li&gt;
&lt;li&gt;generated file contents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can still learn whether the product works.&lt;/p&gt;

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

&lt;p&gt;Tool opened&lt;br&gt;
-&amp;gt; Processing started&lt;br&gt;
-&amp;gt; Processing succeeded&lt;/p&gt;

&lt;p&gt;That tells you whether users can complete a workflow.&lt;/p&gt;

&lt;p&gt;You usually do not need to know what was inside their document.&lt;/p&gt;

&lt;p&gt;The rule I like is:&lt;/p&gt;

&lt;p&gt;Analytics should answer "Did the workflow work?" without answering "What was inside the user's file?"&lt;/p&gt;

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

&lt;h1&gt;
  
  
  7. HONEST LIMITATIONS BUILD MORE TRUST THAN PERFECT-SOUNDING CLAIMS
&lt;/h1&gt;

&lt;p&gt;Some file operations are relatively straightforward.&lt;/p&gt;

&lt;p&gt;Others are not.&lt;/p&gt;

&lt;p&gt;Examples of difficult workflows include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF to Word&lt;/li&gt;
&lt;li&gt;Word to PDF&lt;/li&gt;
&lt;li&gt;OCR&lt;/li&gt;
&lt;li&gt;PDF repair&lt;/li&gt;
&lt;li&gt;complex spreadsheets&lt;/li&gt;
&lt;li&gt;unusual fonts&lt;/li&gt;
&lt;li&gt;forms&lt;/li&gt;
&lt;li&gt;embedded objects&lt;/li&gt;
&lt;li&gt;complicated page layouts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Browser libraries are powerful, but they do not perfectly reproduce every edge case.&lt;/p&gt;

&lt;p&gt;If a conversion may simplify a complex layout, tell the user before processing.&lt;/p&gt;

&lt;p&gt;If transparency must be flattened, explain it.&lt;/p&gt;

&lt;p&gt;If a very large file may exceed the browser's practical memory limit, say so.&lt;/p&gt;

&lt;p&gt;Users can understand limitations.&lt;/p&gt;

&lt;p&gt;What destroys trust is discovering them only after downloading the result.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  WHY LOCAL PROCESSING IS INTERESTING
&lt;/h1&gt;

&lt;p&gt;Server-side processing still has real advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;predictable compute&lt;/li&gt;
&lt;li&gt;larger memory pools&lt;/li&gt;
&lt;li&gt;powerful native libraries&lt;/li&gt;
&lt;li&gt;easier handling of huge files&lt;/li&gt;
&lt;li&gt;consistent execution environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local processing has different advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;files can stay on the user's device&lt;/li&gt;
&lt;li&gt;no upload wait&lt;/li&gt;
&lt;li&gt;reduced server-side exposure&lt;/li&gt;
&lt;li&gt;some workflows can continue offline&lt;/li&gt;
&lt;li&gt;less processing infrastructure is required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I do not think the right question is:&lt;/p&gt;

&lt;p&gt;"Should everything run locally?"&lt;/p&gt;

&lt;p&gt;A better question is:&lt;/p&gt;

&lt;p&gt;"Does this operation genuinely require the user's file to leave their device?"&lt;/p&gt;

&lt;p&gt;For many routine PDF and image operations, modern browsers increasingly make the answer:&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  OPEN SOURCE AND INSPECTABILITY
&lt;/h1&gt;

&lt;p&gt;SoraFiles is open source under AGPL-3.0.&lt;/p&gt;

&lt;p&gt;Open source does not automatically make software secure.&lt;/p&gt;

&lt;p&gt;It does not replace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;testing&lt;/li&gt;
&lt;li&gt;dependency review&lt;/li&gt;
&lt;li&gt;secure implementation&lt;/li&gt;
&lt;li&gt;privacy engineering&lt;/li&gt;
&lt;li&gt;vulnerability management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it makes architectural claims easier to inspect.&lt;/p&gt;

&lt;p&gt;People can look at the code and see how the processing path works.&lt;/p&gt;

&lt;p&gt;For privacy-focused software, that inspectability matters.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  CLOSING THOUGHT
&lt;/h1&gt;

&lt;p&gt;Building file processing directly inside the browser changed the way I think about privacy engineering.&lt;/p&gt;

&lt;p&gt;The strongest privacy improvement was not another paragraph in a privacy policy.&lt;/p&gt;

&lt;p&gt;It was removing unnecessary data movement from the architecture.&lt;/p&gt;

&lt;p&gt;Once that decision is made, privacy becomes an engineering problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory management&lt;/li&gt;
&lt;li&gt;workers&lt;/li&gt;
&lt;li&gt;cancellation&lt;/li&gt;
&lt;li&gt;state machines&lt;/li&gt;
&lt;li&gt;browser compatibility&lt;/li&gt;
&lt;li&gt;output validation&lt;/li&gt;
&lt;li&gt;honest limitations&lt;/li&gt;
&lt;li&gt;carefully separated analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That work is less visible than putting a privacy badge on a landing page.&lt;/p&gt;

&lt;p&gt;It is also what makes the privacy claim meaningful.&lt;/p&gt;

&lt;p&gt;If the user's device is already capable of doing the work, uploading their private file should not automatically be the default.&lt;/p&gt;

&lt;p&gt;SoraFiles:&lt;br&gt;
&lt;a href="https://sorafiles.com" rel="noopener noreferrer"&gt;https://sorafiles.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source code:&lt;br&gt;
&lt;a href="https://github.com/Sora-Labs2026/SoraFiles" rel="noopener noreferrer"&gt;https://github.com/Sora-Labs2026/SoraFiles&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Sora Labs develops SoraFiles.&lt;/p&gt;

&lt;p&gt;SoraFiles is used here as the practical case study for the engineering lessons described in this article.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>privacy</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
