<?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: Matvey</title>
    <description>The latest articles on DEV Community by Matvey (@ronanru).</description>
    <link>https://dev.to/ronanru</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%2F557804%2F62c88fba-640b-4eb1-9477-bfb5db8d755d.png</url>
      <title>DEV Community: Matvey</title>
      <link>https://dev.to/ronanru</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ronanru"/>
    <language>en</language>
    <item>
      <title>How to set up Vercel Image Optimization in TanStack Start</title>
      <dc:creator>Matvey</dc:creator>
      <pubDate>Tue, 17 Jun 2025 21:06:58 +0000</pubDate>
      <link>https://dev.to/ronanru/how-to-set-up-vercel-image-optimization-in-tanstack-start-21ed</link>
      <guid>https://dev.to/ronanru/how-to-set-up-vercel-image-optimization-in-tanstack-start-21ed</guid>
      <description>&lt;p&gt;While Vercel only provides packages with the &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; component for Next.js, Nuxt, and Astro, you can enable Image Optimization in any framework with the &lt;a href="https://vercel.com/docs/build-output-api" rel="noopener noreferrer"&gt;Vercel Build Output API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Editing the Vercel config
&lt;/h2&gt;

&lt;p&gt;The first thing we need to do is tell Vercel that we want to use the Image Optimization API. To do that, we need to modify the bundler to add additional parameters to the &lt;code&gt;.vercel/output/config.json&lt;/code&gt; file after the build. TanStack Start uses &lt;a href="https://nitro.build/" rel="noopener noreferrer"&gt;Nitro&lt;/a&gt; under the hood, and it provides an easy way to do this. All you need to do is create the &lt;code&gt;nitro.config.ts&lt;/code&gt; file with the following content:&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="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNitroConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;vercel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;domains&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="s2"&gt;yourdomain.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// Change to your domain&lt;/span&gt;
        &lt;span class="na"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;828&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3840&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;minimumCacheTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;formats&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="s2"&gt;image/webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="c1"&gt;// Next.js uses the same values for sizes, cacheTTL, and formats&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Implementing the optimization strategy
&lt;/h2&gt;

&lt;p&gt;Now that we've set the config option, when you deploy your app to Vercel, it will proxy the &lt;code&gt;/_vercel/image&lt;/code&gt; endpoint to the image optimization service. Now we just need to implement the component to take advantage of it. The &lt;code&gt;next/image&lt;/code&gt; package source code has thousands of lines of code, because it needs to support multiple optimization services and multiple different strategies for optimization. Most apps don't need that and we can simplify the logic to this simple hook.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMemo&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="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageWidths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;750&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;828&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;3840&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ImgWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;imageWidths&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="kr"&gt;number&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;getVercelOptimizedUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ImgWidth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;w&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;q&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="s2"&gt;75&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`/_vercel/image?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useVercelOptimizedImageProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;useMemo&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&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;meta&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;VITE_VERCEL_ENV&lt;/span&gt; &lt;span class="o"&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;meta&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;VITE_VERCEL_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;development&lt;/span&gt;&lt;span class="dl"&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;{&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;widths&lt;/span&gt; &lt;span class="o"&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;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;imageWidths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;imageWidths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ImgWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ImgWidth&lt;/span&gt;&lt;span class="o"&gt;&amp;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;{&lt;/span&gt;
      &lt;span class="na"&gt;srcSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;widths&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;getVercelOptimizedUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;x`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;widths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ImageProps&lt;/span&gt; &lt;span class="kd"&gt;extends&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;ImgHTMLAttributes&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLImageElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;loading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lazy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ImageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imgProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useVercelOptimizedImageProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;loading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;imgProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="si"&gt;}&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;This component will work exactly the same as the &lt;code&gt;next/image&lt;/code&gt; component in 99% of cases.&lt;/p&gt;

&lt;p&gt;I hope this post helped you!&lt;/p&gt;

&lt;p&gt;If you liked it, follow me on &lt;a href="https://x.com/matveydev" rel="noopener noreferrer"&gt;X, the everything app (Formerly Twitter) (Blaze your glory!)&lt;/a&gt; or on &lt;a href="https://bsky.app/profile/matvey.dev" rel="noopener noreferrer"&gt;BlueSky&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>vercel</category>
      <category>typescript</category>
      <category>react</category>
    </item>
    <item>
      <title>Lucia Auth or NextAuth, which one to use in a Next.js project?</title>
      <dc:creator>Matvey</dc:creator>
      <pubDate>Sun, 22 Oct 2023 17:48:12 +0000</pubDate>
      <link>https://dev.to/ronanru/lucia-auth-or-nextauth-which-one-to-use-in-a-nextjs-project-31bo</link>
      <guid>https://dev.to/ronanru/lucia-auth-or-nextauth-which-one-to-use-in-a-nextjs-project-31bo</guid>
      <description>&lt;p&gt;Most modern apps require some form of authentication. There are many ways to implement it for a new Next.js app. You can, of course, roll your own auth, creating all necessary endpoints and writing all code for creating auth tokens yourself, only using the &lt;code&gt;crypto&lt;/code&gt; module. I think every dev should do auth this way at least once, just to understand how everything works on a deeper level, but this approach involves a lot of boilerplate code, and even the smallest bug in the auth implementation can become a critical vulnerability. That's why many devs choose to use third-party services like Clerk or Supabase. They make it super easy to add simple authentication, but the moment after you reach more users than the free plan allows, they can become really pricey. The third and, in my opinion, the best way is to use an open-source auth library, like Auth.js or Lucia. But which one should you choose?&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth.js
&lt;/h2&gt;

&lt;p&gt;Let's first talk about Auth.js. It started out as NextAuth, a library for Next.js, but now it features adapters for SvelteKit and SolidStart. With it, you create a new API endpoint (or a route handler with &lt;code&gt;/app&lt;/code&gt;) and export a handler, provided by the library.&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="c1"&gt;// /src/pages/api/auth/[...nextauth].ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextAuth&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&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="nc"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="cm"&gt;/* Configuration here */&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It provides bindings to get the current user both on the client (with the &lt;code&gt;useSession()&lt;/code&gt; hook) and server (&lt;code&gt;getServerSession()&lt;/code&gt;). It automatically creates a sign-in page with all necessary inputs, handling all errors, and displaying them to the user (But you can still create your own login page, and most people do).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lucia Auth
&lt;/h2&gt;

&lt;p&gt;Lucia is a newer, much simpler library. It works on any TypeScript server or framework but requires a bit more setup. It's a lot simpler to understand and much lighter. In their docs, the creator of Lucia calls Auth.js too bloated and opinionated. Instead of occupying its own endpoint, it gives you simple functions like &lt;code&gt;createSessionCookie()&lt;/code&gt; or &lt;code&gt;createUser()&lt;/code&gt;. With them, you can create an endpoint however you want; you can use tRPC, for example. The other difference is Lucia works only on the server, but you can pass the current session data to the client with a server component or &lt;code&gt;getServerSideProps&lt;/code&gt; and use your favorite react state library to store it. To log out a user with Auth.js, we just need to call a &lt;code&gt;logOut()&lt;/code&gt; function in the react code, but with Lucia, you need to create a new API endpoint where you call &lt;code&gt;invalidateSession()&lt;/code&gt; and &lt;code&gt;setSession(null)&lt;/code&gt;, and send a request to this endpoint from the frontend. It's more steps, but it's more flexible, and you can choose exactly how you implement everything. When you set up your project, you can copy most of the code from the &lt;a href="https://github.com/lucia-auth/examples" rel="noopener noreferrer"&gt;examples repo&lt;/a&gt;, so it's not harder to set up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;p&gt;Both libraries work great with Next.js and different databases and ORMs like Prisma and Drizzle; both support all OAuth services you may need.&lt;/p&gt;

&lt;p&gt;So, which one should you use? That depends on the situation. If you just need a simple "Sign in with Discord" button, NextAuth is the fastest way to add it, but I will recommend Lucia if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need to add custom logic to the auth flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you need to add your own logic to the auth flow (for example, allow only users with a certain domain in their email to sign up), it's easy with Lucia: you search to where you call &lt;code&gt;createUser&lt;/code&gt; and add an if statement, but with Auth.js, you need to dig through their documentation to find how to add a handler to a &lt;code&gt;createUser&lt;/code&gt; event.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Need to use email and password auth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Auth.js is great for adding "Sign in with Discord" or "Sign in with GitHub" buttons, but they make creating email/password auth intentionally hard, to discourage newer devs from using it, due to security risks. Lucia, on the other hand, provides all necessary documentation and functions to create an email and password login.&lt;/p&gt;

&lt;p&gt;In any other situations, both libraries are great.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>nextjs</category>
      <category>authjs</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How I made my resume with HTML and Tailwind CSS</title>
      <dc:creator>Matvey</dc:creator>
      <pubDate>Sat, 04 Mar 2023 21:33:13 +0000</pubDate>
      <link>https://dev.to/ronanru/how-i-made-my-resume-with-html-and-tailwind-css-2676</link>
      <guid>https://dev.to/ronanru/how-i-made-my-resume-with-html-and-tailwind-css-2676</guid>
      <description>&lt;p&gt;My first resume was hastily put together using LibreOffice Writer. However, I soon realized that to increase my chances of getting hired, I needed to create a standout resume that would set me apart from other applicants. Since I didn't want to spend money on an expensive template or learn how to use a PDF editor, I decided to find a way to create a PDF using web technologies. In this post, I will share how I used HTML and Tailwind CSS to create my resume.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting the project
&lt;/h2&gt;

&lt;p&gt;I prefer using Tailwind CSS, and the easiest way to create a basic web page with Tailwind is by using &lt;a href="https://astro.build" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;. I began by creating a new Astro project with &lt;code&gt;npm create astro@latest&lt;/code&gt; using the Empty template. Then, I added Tailwind by running &lt;code&gt;npx astro add tailwind&lt;/code&gt;. Now, I have a blank web page that I can customize with HTML and Tailwind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a page
&lt;/h2&gt;

&lt;p&gt;The webpage is responsive, but the resume must be strictly standard size. I tackled this with a very interesting trick. Did you know &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Values_and_units#absolute_length_units" rel="noopener noreferrer"&gt;you can use centimeters and inches as a css unit&lt;/a&gt;? I added &lt;code&gt;h-[297mm] w-[210mm]&lt;/code&gt; classes to an element to make it the exact size of a standard A4 paper. Here's the basic template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My Cool Resume&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid min-h-screen place-items-center bg-gray-400"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-4 h-[297mm] w-[210mm] overflow-hidden rounded-md bg-white p-8 shadow-lg"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Put your content here --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fdov4f2d809mckkp55ev0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdov4f2d809mckkp55ev0.png" alt="A blank A4 page" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Tech Logos
&lt;/h2&gt;

&lt;p&gt;After filling in the resume, I wanted to also add logos for different programming languages and technologies I have in my resume.&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.amazonaws.com%2Fuploads%2Farticles%2F9nquhgns1fz6xe38zf6l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nquhgns1fz6xe38zf6l.png" alt="A list of programming languages with logos" width="565" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luckily, I found that in astro you can do it easily with the &lt;code&gt;astro-icon&lt;/code&gt; package. This package uses &lt;a href="https://iconify.design/" rel="noopener noreferrer"&gt;Iconify&lt;/a&gt;, an icon library that combines dozens of different icon packs into one. The most interesting icon pack for me was the &lt;a href="https://icon-sets.iconify.design/logos/" rel="noopener noreferrer"&gt;SVG Logos&lt;/a&gt; as it includes all logos for different tech companies, programming languages, etc. Now, adding these icons is as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Icon&lt;/span&gt; &lt;span class="na"&gt;pack=&lt;/span&gt;&lt;span class="s"&gt;"logos"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"rust"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inline h-5 w-5"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; Rust,
&lt;span class="nt"&gt;&amp;lt;Icon&lt;/span&gt; &lt;span class="na"&gt;pack=&lt;/span&gt;&lt;span class="s"&gt;"logos"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"python"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inline h-5 w-5"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt; Python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Converting site to PDF
&lt;/h2&gt;

&lt;p&gt;Now that my new resume looks great, the question was how to convert it to a PDF. In the print menu (Ctrl+P) you can choose a special "Save as PDF" printer and save the page as a PDF. However, when I did it for the first time, I noticed it wasn't perfect, the margins were a lot bigger, background for some elements was missing, etc. Some issues I fixed with CSS, for some I just needed to adjust a setting in the printing menu. Tailwind has a &lt;a href="https://tailwindcss.com/docs/hover-focus-and-other-states#print-styles" rel="noopener noreferrer"&gt;&lt;code&gt;print:&lt;/code&gt;&lt;/a&gt; prefix for styles that should only be applied while printing. I used it to remove all margins and set the page to use the full width and height while printing. Here's what my updated template looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;My Cool Resume&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid min-h-screen place-items-center bg-gray-400 print:min-h-0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt;
      &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"m-4 h-[297mm] w-[210mm] overflow-hidden rounded-md bg-white p-8 shadow-lg print:m-0 print:h-screen print:w-screen print:rounded-none print:shadow-none"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- Put your content here --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, to convert the page correctly, in the print options, under "More Settings" I set "Margins" to "None" and enables the "Background graphics" option.&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.amazonaws.com%2Fuploads%2Farticles%2Frd3tthoknenid8b57s52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frd3tthoknenid8b57s52.png" alt="A screenshot of the print options" width="370" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After making these changes, it converted my resume without any issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope this post was useful for you. &lt;a href="https://github.com/ronanru/resume" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is my finished resume. If you use a method described in this post to make a resume, please share it with me &lt;a href="https://twitter.com/ryabchikovm" rel="noopener noreferrer"&gt;on Twitter&lt;/a&gt;. Thanks for reading!&lt;/p&gt;

</description>
      <category>html</category>
      <category>tailwindcss</category>
      <category>astro</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Using Express.js in 2023</title>
      <dc:creator>Matvey</dc:creator>
      <pubDate>Wed, 01 Mar 2023 02:10:25 +0000</pubDate>
      <link>https://dev.to/ronanru/stop-using-expressjs-in-202-3kc</link>
      <guid>https://dev.to/ronanru/stop-using-expressjs-in-202-3kc</guid>
      <description>&lt;p&gt;For years, express.js has been the standard choice for building REST APIs with JavaScript or TypeScript. It has around 30 million weekly downloads (twice as much as React), and nearly all beginner tutorials recommend it. However, in today's landscape, there are now alternatives that offer better developer experiences and performance. In this post, we'll explore some of these alternatives and why you should consider them over Express.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's wrong with Express?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. It's Unmaintained
&lt;/h3&gt;

&lt;p&gt;Express hasn't seen any major updates or new features in years, with its website last being updated in 2017.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. It's Slow
&lt;/h3&gt;

&lt;p&gt;Since Express is primarily written in pre-ES6 JavaScript, it misses out on years of performance-enhancing updates and concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. It doesn't work well with TypeScript
&lt;/h3&gt;

&lt;p&gt;Express wasn't designed with TypeScript in mind and requires a separate types package to work with it. Despite this, you will still encounter lots of &lt;code&gt;any&lt;/code&gt;s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives to Express
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fastify
&lt;/h3&gt;

&lt;p&gt;Fastify is a modern alternative to Express. It's significantly faster and has a vast ecosystem of plugins. It shares a similar syntax with Express, so most of your Express knowledge will still apply to Fastify. With &lt;code&gt;@fastify/express&lt;/code&gt; package, you can even use any express middleware module with fastify. Fastify is the most similar to express out of these alternatives, and It's the easiest to switch to. Use Fastify instead of Express for your next API to get instant performance and DX improvements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.fastify.io/" rel="noopener noreferrer"&gt;Learn more about Fastify&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Hono
&lt;/h3&gt;

&lt;p&gt;Hono is a simple and minimal web framework that is even faster than Fastify. Although it lacks many of plugins, it can still handle most use cases. The primary selling point of Hono is that it can be used with runtimes other than Node.js, like Deno, Bun, or Cloudflare Edge Workers. Using Hono with these runtimes can make it even faster, cheaper and easier to host.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hono.dev/" rel="noopener noreferrer"&gt;Learn more about Hono&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  tRPC
&lt;/h3&gt;

&lt;p&gt;If you're building an API solely to consume it in a TypeScript client application (such as a React app), consider using tRPC. With this library, you can declare a function on your backend, and after a simple setup, call it on the frontend. tRPC automatically infers the input and return types of the function, resulting in TypeScript error messages and IntelliSense.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://trpc.io/" rel="noopener noreferrer"&gt;Learn more about tRPC&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mqtt</category>
      <category>mcp</category>
      <category>iot</category>
    </item>
  </channel>
</rss>
