<?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: Knipper Mccarville</title>
    <description>The latest articles on DEV Community by Knipper Mccarville (@knipper_mccarville_6f3605).</description>
    <link>https://dev.to/knipper_mccarville_6f3605</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%2F3903347%2F0d13eac8-61cd-4f54-beb4-a698b8f92b4b.png</url>
      <title>DEV Community: Knipper Mccarville</title>
      <link>https://dev.to/knipper_mccarville_6f3605</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/knipper_mccarville_6f3605"/>
    <language>en</language>
    <item>
      <title>How I Built a NASA-Style Name Generator with Next.js and Canvas API</title>
      <dc:creator>Knipper Mccarville</dc:creator>
      <pubDate>Wed, 29 Apr 2026 01:40:31 +0000</pubDate>
      <link>https://dev.to/knipper_mccarville_6f3605/how-i-built-a-nasa-style-name-generator-with-nextjs-and-canvas-api-25km</link>
      <guid>https://dev.to/knipper_mccarville_6f3605/how-i-built-a-nasa-style-name-generator-with-nextjs-and-canvas-api-25km</guid>
      <description>&lt;p&gt;Ever wondered what your name looks like from space? I built &lt;a href="https://yournameinlandsat.online" rel="noopener noreferrer"&gt;Your Name in Landsat&lt;/a&gt; — a web app that spells out any name or short phrase using real Landsat satellite imagery tiles shaped like alphabet letters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;NASA's Landsat program has been capturing Earth observation data since 1972. Among the millions of satellite images, some natural and man-made features on Earth's surface happen to resemble letters of the alphabet. I thought it would be fun to collect these "letter tiles" and let people spell their names with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; with App Router for server-side rendering and routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React 19&lt;/strong&gt; for the interactive UI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; for type safety&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS 4&lt;/strong&gt; for responsive styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Canvas API&lt;/strong&gt; for compositing letter tiles into a single shareable image&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Workers&lt;/strong&gt; for edge deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The user types a name or short phrase&lt;/li&gt;
&lt;li&gt;The app maps each character to a pre-curated Landsat satellite image tile&lt;/li&gt;
&lt;li&gt;Canvas API composites all tiles side by side into one panoramic image&lt;/li&gt;
&lt;li&gt;The result can be downloaded or shared on social media&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The letter tiles come from actual Landsat scenes — rivers that curve like an "S", urban grids that form an "H", coastlines shaped like a "C", and so on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Technical Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Image Loading and Canvas Compositing
&lt;/h3&gt;

&lt;p&gt;Loading multiple high-resolution satellite tiles and stitching them together required careful management of async image loading. I used &lt;code&gt;Promise.all&lt;/code&gt; to ensure all tiles are loaded before drawing:&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;loadTile&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;img&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;Image&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crossOrigin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;anonymous&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&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="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onerror&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;src&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;tiles&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;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;letters&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;letter&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loadTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/tiles/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.webp`&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;h3&gt;
  
  
  Responsive Layout
&lt;/h3&gt;

&lt;p&gt;Each letter tile is square, but the total width depends on the name length. I had to dynamically calculate canvas dimensions and handle viewport constraints so the result looks good on both desktop and mobile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Deployment
&lt;/h3&gt;

&lt;p&gt;Deploying on Cloudflare Workers meant optimizing for cold start times. Next.js 16's built-in edge runtime support made this straightforward — static assets are cached at the edge while dynamic routes run in Workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;You can try the &lt;a href="https://yournameinlandsat.online" rel="noopener noreferrer"&gt;Landsat Name Generator&lt;/a&gt; yourself. Type your name and see what it looks like spelled with satellite imagery from space!&lt;/p&gt;

&lt;p&gt;The project is open source and I'd love feedback from the community. Let me know what you think in the comments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Next.js, React, and real NASA Landsat satellite data.&lt;/em&gt;&lt;/p&gt;

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