<?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: xiaowei</title>
    <description>The latest articles on DEV Community by xiaowei (@xiaowei_f1158ef66ba40e6e0).</description>
    <link>https://dev.to/xiaowei_f1158ef66ba40e6e0</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%2F3829049%2F12483170-0c67-4074-a0b7-b14fa8faa9d5.png</url>
      <title>DEV Community: xiaowei</title>
      <link>https://dev.to/xiaowei_f1158ef66ba40e6e0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xiaowei_f1158ef66ba40e6e0"/>
    <language>en</language>
    <item>
      <title>Equity vs Salary: How to Compare a Startup Offer Without Fooling Yourself</title>
      <dc:creator>xiaowei</dc:creator>
      <pubDate>Thu, 26 Mar 2026 08:27:39 +0000</pubDate>
      <link>https://dev.to/xiaowei_f1158ef66ba40e6e0/equity-vs-salary-how-to-compare-a-startup-offer-without-fooling-yourself-2o9p</link>
      <guid>https://dev.to/xiaowei_f1158ef66ba40e6e0/equity-vs-salary-how-to-compare-a-startup-offer-without-fooling-yourself-2o9p</guid>
      <description>&lt;h1&gt;
  
  
  Equity vs Salary: How to Compare a Startup Offer Without Fooling Yourself
&lt;/h1&gt;

&lt;p&gt;A lot of job offers try to make lower cash feel acceptable by showing a big equity number.&lt;/p&gt;

&lt;p&gt;Sometimes that trade-off is worth it.&lt;br&gt;
Often it isn't.&lt;/p&gt;

&lt;p&gt;The mistake most candidates make is treating equity like delayed salary. It isn't. Salary is money you can use next month. Equity is a future possibility that depends on vesting, dilution, company performance, and whether a real liquidity event ever happens.&lt;/p&gt;

&lt;p&gt;If you compare them as if they are equally real, you will almost always overvalue the upside story.&lt;/p&gt;

&lt;p&gt;Here is the framework I use when comparing an offer with more salary against an offer with more equity.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with the cash gap, not the narrative
&lt;/h2&gt;

&lt;p&gt;Before you get pulled into the startup story, calculate the real cash difference.&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much lower is the base salary?&lt;/li&gt;
&lt;li&gt;How much does that change my monthly life?&lt;/li&gt;
&lt;li&gt;Does the lower salary create stress around rent, family, debt, or savings?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A lot of people say they are "fine with less cash" in theory. In practice, lower cash often changes how much pressure they feel every week.&lt;/p&gt;

&lt;p&gt;That matters.&lt;/p&gt;

&lt;p&gt;If the lower-salary offer makes your real life tighter, the equity has to clear a much higher bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Treat equity as uncertain upside, not guaranteed compensation
&lt;/h2&gt;

&lt;p&gt;This is the core mental model.&lt;/p&gt;

&lt;p&gt;Salary is guaranteed unless you get laid off.&lt;br&gt;
Equity is probabilistic.&lt;/p&gt;

&lt;p&gt;Its value depends on things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;vesting schedule&lt;/li&gt;
&lt;li&gt;dilution over time&lt;/li&gt;
&lt;li&gt;exercise cost&lt;/li&gt;
&lt;li&gt;company survival&lt;/li&gt;
&lt;li&gt;future valuation growth&lt;/li&gt;
&lt;li&gt;whether the company ever reaches liquidity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when a company says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The package is lower in cash, but the equity could be worth a lot."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important word is &lt;strong&gt;could&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That doesn't make equity useless. It just means you should discount it heavily unless you have strong reasons not to.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ask whether the company quality is high enough for equity to matter at all
&lt;/h2&gt;

&lt;p&gt;Not all equity deserves serious weight.&lt;/p&gt;

&lt;p&gt;If the business is weak, unstable, or vague about fundamentals, the equity may be more motivational theater than compensation.&lt;/p&gt;

&lt;p&gt;I would ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the company growing or stalling?&lt;/li&gt;
&lt;li&gt;Do I trust the leadership?&lt;/li&gt;
&lt;li&gt;Is there real traction?&lt;/li&gt;
&lt;li&gt;How strong is the funding or revenue base?&lt;/li&gt;
&lt;li&gt;Would I still want this job if the equity ended up being worth nothing?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is especially important.&lt;/p&gt;

&lt;p&gt;If the answer is no, the offer is probably too dependent on a best-case scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Compare career upside separately from financial upside
&lt;/h2&gt;

&lt;p&gt;People often bundle these together, but they are different.&lt;/p&gt;

&lt;p&gt;Sometimes a lower-cash startup offer is still the better decision because it gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;much broader scope&lt;/li&gt;
&lt;li&gt;stronger ownership&lt;/li&gt;
&lt;li&gt;faster learning&lt;/li&gt;
&lt;li&gt;better long-term positioning&lt;/li&gt;
&lt;li&gt;a more impressive story for the next role&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that case, the decision is not really "salary vs equity."&lt;br&gt;
It is more like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;lower salary today in exchange for better career capital + some upside&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That can be a smart move.&lt;/p&gt;

&lt;p&gt;But if the lower-cash role is also weaker on manager quality, stability, and day-to-day fit, then the equity story is doing too much work.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Watch for the four ways candidates overvalue equity
&lt;/h2&gt;

&lt;p&gt;I see the same mistakes over and over:&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Anchoring on the headline number
&lt;/h3&gt;

&lt;p&gt;A company mentions a big potential number and your brain starts treating it as half-real.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Ignoring dilution
&lt;/h3&gt;

&lt;p&gt;Your percentage today is not necessarily your effective value later.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Confusing paper value with liquid value
&lt;/h3&gt;

&lt;p&gt;A valuation on paper is not the same as cash in your account.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Underestimating personal cash pressure
&lt;/h3&gt;

&lt;p&gt;Lower salary sounds manageable until it starts affecting your life every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Use a simple comparison table
&lt;/h2&gt;

&lt;p&gt;When I want to get more objective, I score each offer across these dimensions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;What to check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cash gap&lt;/td&gt;
&lt;td&gt;How much monthly breathing room do I lose?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Company quality&lt;/td&gt;
&lt;td&gt;Is the upside story credible enough to count?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Equity terms&lt;/td&gt;
&lt;td&gt;Vesting, dilution, exercise, liquidity path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Career upside&lt;/td&gt;
&lt;td&gt;Scope, learning, network, future signaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lifestyle fit&lt;/td&gt;
&lt;td&gt;Can I live with the lower salary without resentment?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk concentration&lt;/td&gt;
&lt;td&gt;Am I stacking company risk, role risk, and cash risk at the same time?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is much better than asking, "Which offer sounds more exciting?"&lt;/p&gt;

&lt;h2&gt;
  
  
  7. When taking less salary for more equity actually makes sense
&lt;/h2&gt;

&lt;p&gt;I think this trade-off can make sense when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have enough financial cushion&lt;/li&gt;
&lt;li&gt;the company quality is genuinely strong&lt;/li&gt;
&lt;li&gt;the role gives much better growth and scope&lt;/li&gt;
&lt;li&gt;you understand the equity terms clearly&lt;/li&gt;
&lt;li&gt;you would still feel okay about the job even if the upside never fully materialized&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: the equity is a bonus on top of an already strong decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. When it usually does &lt;em&gt;not&lt;/em&gt; make sense
&lt;/h2&gt;

&lt;p&gt;I would be much more cautious when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you need the higher cash for real life stability&lt;/li&gt;
&lt;li&gt;the company is vague about equity details&lt;/li&gt;
&lt;li&gt;the company is risky and the role itself is not especially compelling&lt;/li&gt;
&lt;li&gt;the offer relies on urgency or hype&lt;/li&gt;
&lt;li&gt;you are telling yourself the upside is basically guaranteed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the classic trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simplest rule
&lt;/h2&gt;

&lt;p&gt;If one offer pays more cash and the other offers more equity, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If the equity ends up being worth little or nothing, would I still be happy I chose this role?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If yes, then the lower-cash offer may still be a smart long-term bet.&lt;br&gt;
If no, you are probably overpaying for uncertainty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The best job decision is usually not the one with the most exciting story.&lt;br&gt;
It is the one that balances:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;financial reality&lt;/li&gt;
&lt;li&gt;career upside&lt;/li&gt;
&lt;li&gt;manager and company quality&lt;/li&gt;
&lt;li&gt;personal risk tolerance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a clearer side-by-side way to think through salary, equity, growth, and risk, I built a free comparison workflow at &lt;a href="https://jobmirror.app/offer-compare" rel="noopener noreferrer"&gt;JobMirror&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And if you want the original long-form version of this topic, the canonical article is here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://jobmirror.app/blog/equity-vs-salary" rel="noopener noreferrer"&gt;https://jobmirror.app/blog/equity-vs-salary&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>career</category>
      <category>salary</category>
      <category>jobsearch</category>
      <category>startup</category>
    </item>
    <item>
      <title>How I Built a Free AI Job Offer Comparison Tool on Cloudflare Pages (Next.js + Edge Runtime)</title>
      <dc:creator>xiaowei</dc:creator>
      <pubDate>Mon, 23 Mar 2026 03:09:26 +0000</pubDate>
      <link>https://dev.to/xiaowei_f1158ef66ba40e6e0/how-i-built-a-free-ai-job-offer-comparison-tool-on-cloudflare-pages-nextjs-edge-runtime-4642</link>
      <guid>https://dev.to/xiaowei_f1158ef66ba40e6e0/how-i-built-a-free-ai-job-offer-comparison-tool-on-cloudflare-pages-nextjs-edge-runtime-4642</guid>
      <description>&lt;p&gt;I built &lt;a href="https://jobmirror.app" rel="noopener noreferrer"&gt;JobMirror&lt;/a&gt; — a free AI toolkit for job seekers — and deployed it entirely on Cloudflare Pages with Next.js. Here's what I learned, what broke, and how I fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built It
&lt;/h2&gt;

&lt;p&gt;Most job seekers face the same three problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They apply to jobs without knowing how well their resume actually matches&lt;/li&gt;
&lt;li&gt;They accept the first offer they get without comparing alternatives&lt;/li&gt;
&lt;li&gt;They leave salary on the table because they don't have data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted to fix all three with a single, free tool. The result is &lt;a href="https://jobmirror.app" rel="noopener noreferrer"&gt;JobMirror&lt;/a&gt;: resume review, job fit analysis, offer comparison, career assessment, and cover letter generation in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; with &lt;code&gt;@cloudflare/next-on-pages&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek API&lt;/strong&gt; for AI inference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;next-auth v5 beta&lt;/strong&gt; for Google OAuth&lt;/li&gt;
&lt;li&gt;No database — JWT sessions + localStorage&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hard Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Edge Runtime Is Not Node.js
&lt;/h3&gt;

&lt;p&gt;Cloudflare Pages runs your code on V8 isolates, not Node.js. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No &lt;code&gt;fs&lt;/code&gt;, no &lt;code&gt;crypto&lt;/code&gt; from Node&lt;/li&gt;
&lt;li&gt;No &lt;code&gt;prisma&lt;/code&gt; (uses native bindings)&lt;/li&gt;
&lt;li&gt;Any library that assumes Node.js internals will silently fail or loudly crash&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every API route needs this at the top:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forget it on one route and you get a cryptic build error or a 500 in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. next-auth v4 Breaks on Edge
&lt;/h3&gt;

&lt;p&gt;next-auth v4 imports Node's &lt;code&gt;crypto&lt;/code&gt; module internally. On Cloudflare Pages, this throws:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Module not found: Can't resolve 'crypto'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix: upgrade to &lt;strong&gt;next-auth v5 beta&lt;/strong&gt; (&lt;code&gt;5.0.0-beta.30&lt;/code&gt;), which is built for Edge from the ground up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;next-auth@5.0.0-beta.30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;v5 also changes the config shape significantly — &lt;code&gt;AUTH_SECRET&lt;/code&gt; instead of &lt;code&gt;NEXTAUTH_SECRET&lt;/code&gt;, and the provider setup is slightly different. Worth it though.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Correct Build + Deploy Command
&lt;/h3&gt;

&lt;p&gt;This tripped me up for a while. The output directory is NOT &lt;code&gt;.next&lt;/code&gt; — it's &lt;code&gt;.vercel/output/static&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build&lt;/span&gt;
npx @cloudflare/next-on-pages@1

&lt;span class="c"&gt;# Deploy&lt;/span&gt;
&lt;span class="nv"&gt;CLOUDFLARE_API_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_account_id"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
npx wrangler pages deploy .vercel/output/static &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--project-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-project &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deploying &lt;code&gt;.next&lt;/code&gt; directly results in a site that returns 404 or 500 on every route. Ask me how I know.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. SessionProvider Can't Be in a Server Component
&lt;/h3&gt;

&lt;p&gt;If you put &lt;code&gt;&amp;lt;SessionProvider&amp;gt;&lt;/code&gt; directly in &lt;code&gt;layout.tsx&lt;/code&gt;, Next.js tries to prerender it as a server component and throws during build. Wrap it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/ClientProviders.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SessionProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next-auth/react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ClientProviders&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;})&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SessionProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;SessionProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use &lt;code&gt;&amp;lt;ClientProviders&amp;gt;&lt;/code&gt; in your layout — works cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. AI on the Edge
&lt;/h3&gt;

&lt;p&gt;For AI inference, I'm calling DeepSeek's API directly from Edge functions. Since it's just HTTP, it works perfectly — no special SDK needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;edge&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&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;Request&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jobDescription&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.deepseek.com/v1/chat/completions&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&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;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="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;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEEPSEEK_API_KEY&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="na"&gt;body&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;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deepseek-chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You are a career coach...&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&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="s2"&gt;`Resume: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n\nJob: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jobDescription&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="na"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&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;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;text/event-stream&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Streaming works out of the box because Edge functions support &lt;code&gt;ReadableStream&lt;/code&gt; natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start with Edge constraints in mind.&lt;/strong&gt; Don't pick libraries and then discover they don't work on Edge — check first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Cloudflare D1 or KV early.&lt;/strong&gt; I'm using localStorage for user data right now, which works but isn't ideal for any kind of persistence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't use &lt;code&gt;pnpm&lt;/code&gt; with Cloudflare Pages.&lt;/strong&gt; The lockfile version mismatch errors are frequent and annoying. Use npm.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jobmirror.app" rel="noopener noreferrer"&gt;JobMirror&lt;/a&gt; is live and free to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎯 &lt;a href="https://jobmirror.app/jd-fit" rel="noopener noreferrer"&gt;Job Fit Analyzer&lt;/a&gt; — paste any JD, get a match score and keyword gaps&lt;/li&gt;
&lt;li&gt;📝 &lt;a href="https://jobmirror.app/resume-review" rel="noopener noreferrer"&gt;Resume Review&lt;/a&gt; — 6-dimension AI audit with specific fixes&lt;/li&gt;
&lt;li&gt;⚖️ &lt;a href="https://jobmirror.app/offer-compare" rel="noopener noreferrer"&gt;Offer Comparison&lt;/a&gt; — side-by-side AI breakdown of multiple offers&lt;/li&gt;
&lt;li&gt;🧠 &lt;a href="https://jobmirror.app/assessment" rel="noopener noreferrer"&gt;Career Assessment&lt;/a&gt; — Big Five personality mapped to role fit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No account required for core tools. Feedback welcome — especially if you've hit similar Edge Runtime issues.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Next.js 14, Cloudflare Pages, and more late nights than I'd like to admit.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>cloudflare</category>
      <category>ai</category>
      <category>career</category>
    </item>
    <item>
      <title>How I Built an AI PDF Summarizer on Cloudflare Edge (Next.js + next-auth v5)</title>
      <dc:creator>xiaowei</dc:creator>
      <pubDate>Tue, 17 Mar 2026 09:39:27 +0000</pubDate>
      <link>https://dev.to/xiaowei_f1158ef66ba40e6e0/how-i-built-an-ai-pdf-summarizer-on-cloudflare-edge-nextjs-next-auth-v5-1j5j</link>
      <guid>https://dev.to/xiaowei_f1158ef66ba40e6e0/how-i-built-an-ai-pdf-summarizer-on-cloudflare-edge-nextjs-next-auth-v5-1j5j</guid>
      <description>&lt;p&gt;The Problem&lt;br&gt;
I was tired of reading 50-page reports just to find 3 key points.&lt;/p&gt;

&lt;p&gt;Existing tools either cost too much, require a desktop install, or just do dumb text extraction with no intelligence. I wanted something that could summarize, explain, and answer questions about any PDF — in the browser, for free.&lt;/p&gt;

&lt;p&gt;So I built SumifyPDF.&lt;/p&gt;

&lt;p&gt;What It Does&lt;br&gt;
🤖 AI Summary — upload a PDF, get key insights in ~10 seconds&lt;br&gt;
💬 Chat with PDF — ask "what are the risks?" or "summarize section 3"&lt;br&gt;
🗺️ Mind Map — auto-generated visual overview&lt;br&gt;
📝 PDF → Word conversion&lt;br&gt;
🖼️ PDF → Image export&lt;br&gt;
🔗 Merge PDFs&lt;br&gt;
🔒 Encrypt / Decrypt&lt;br&gt;
📷 OCR for scanned documents&lt;br&gt;
All running in the browser. No install. Free to start.&lt;/p&gt;

&lt;p&gt;Tech Stack&lt;br&gt;
Next.js 14 on Cloudflare Pages (Edge Runtime)&lt;br&gt;
Advanced AI models for summarization and chat&lt;br&gt;
OCR API for scanned document support&lt;br&gt;
next-auth v5 beta for Google OAuth&lt;br&gt;
PayPal for Pro subscriptions&lt;br&gt;
Cloudflare Workers for all API routes&lt;br&gt;
The Hard Parts&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloudflare Edge + next-auth = Pain
This was the biggest headache. next-auth v4 depends on Node.js crypto module — which doesn't exist on Cloudflare Edge Runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The error:&lt;/p&gt;

&lt;p&gt;Module not found: Can't resolve 'crypto'&lt;br&gt;
​&lt;br&gt;
Fix: Migrate to next-auth v5 beta (5.0.0-beta.30), which is Edge-compatible.&lt;/p&gt;

&lt;p&gt;// src/lib/auth.ts&lt;br&gt;
import NextAuth from 'next-auth';&lt;br&gt;
import Google from 'next-auth/providers/google';&lt;/p&gt;

&lt;p&gt;export const { handlers, auth, signIn, signOut } = NextAuth({&lt;br&gt;
  providers: [Google],&lt;br&gt;
  trustHost: true,&lt;br&gt;
  session: { strategy: 'jwt' },&lt;br&gt;
});&lt;br&gt;
​&lt;br&gt;
// src/app/api/auth/[...nextauth]/route.ts&lt;br&gt;
export const runtime = 'edge'; // ← this is the key line&lt;br&gt;
export { handlers as GET, handlers as POST } from '@/lib/auth';&lt;br&gt;
​&lt;br&gt;
Also: SessionProvider can't be used directly in layout.tsx — it causes prerender crashes on /_not-found. Wrap it in a 'use client' component:&lt;/p&gt;

&lt;p&gt;// src/components/ClientProviders.tsx&lt;br&gt;
'use client';&lt;br&gt;
import { SessionProvider } from 'next-auth/react';&lt;br&gt;
export function AuthProvider({ children }: { children: React.ReactNode }) {&lt;br&gt;
  return {children};&lt;br&gt;
}&lt;br&gt;
​&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrong Deploy Path
First deployment: the site showed only 404 pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I was deploying .next/export — which only contains the 404/500 pages when using next-on-pages.&lt;/p&gt;

&lt;p&gt;Correct path:&lt;/p&gt;

&lt;p&gt;npx @cloudflare/next-on-pages@1&lt;br&gt;
npx wrangler pages deploy .vercel/output/static --project-name=my-project&lt;br&gt;
​&lt;br&gt;
The build output goes to .vercel/output/static, not .next/.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prisma / SQLite on Edge
Early version used Prisma + SQLite for session storage. Edge Runtime doesn't support Node.js file system APIs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fix: Switched to JWT sessions (stateless). No database needed for auth.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pnpm Lockfile Conflicts
Cloudflare Pages build kept failing with:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;ERR_PNPM_OUTDATED_LOCKFILE&lt;br&gt;
​&lt;br&gt;
Fix: Delete pnpm-lock.yaml and let it regenerate. Or switch to npm.&lt;/p&gt;

&lt;p&gt;Architecture Overview&lt;br&gt;
User Browser&lt;br&gt;
    │&lt;br&gt;
    ▼&lt;br&gt;
Cloudflare Pages (Edge)&lt;br&gt;
    ├── Next.js App Router (SSR on Edge)&lt;br&gt;
    ├── /api/summarize  → AI summarization&lt;br&gt;
    ├── /api/chat       → Conversational Q&amp;amp;A&lt;br&gt;
    ├── /api/ocr        → Scanned PDF processing&lt;br&gt;
    └── /api/auth       → next-auth v5 (Edge)&lt;br&gt;
​&lt;br&gt;
All API routes run as Cloudflare Workers — globally distributed, ~0 cold start.&lt;/p&gt;

&lt;p&gt;What I'd Do Differently&lt;br&gt;
Start with next-auth v5 — don't waste time on v4 for Edge projects&lt;br&gt;
Test the deploy path early — wrangler pages deploy has specific requirements&lt;br&gt;
Keep auth stateless — JWT sessions are simpler and Edge-compatible&lt;br&gt;
Ship fast, iterate — the first version had 3 features. Now it has 8.&lt;br&gt;
Try It&lt;br&gt;
👉 &lt;a href="https://sumifypdf.com" rel="noopener noreferrer"&gt;sumifypdf.com&lt;/a&gt; — free to use, no signup required for basic features.&lt;/p&gt;

&lt;p&gt;If you're building on Cloudflare Edge with Next.js, happy to answer questions in the comments. The auth + Edge combination has a lot of undocumented gotchas.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>nextjs</category>
      <category>cloudflarechallenge</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
