<?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: 冠</title>
    <description>The latest articles on DEV Community by 冠 (@_eb3468ba11d30aaf81a0e85).</description>
    <link>https://dev.to/_eb3468ba11d30aaf81a0e85</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%2F3454222%2F6cb9b375-4dde-456b-9d3e-1216f849a798.png</url>
      <title>DEV Community: 冠</title>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_eb3468ba11d30aaf81a0e85"/>
    <language>en</language>
    <item>
      <title>How I Built an SEO-Friendly AI Video Ideas Hub in Next.js</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Fri, 09 Oct 2026 13:37:12 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/how-i-built-an-seo-friendly-ai-video-ideas-hub-in-nextjs-3g8</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/how-i-built-an-seo-friendly-ai-video-ideas-hub-in-nextjs-3g8</guid>
      <description>&lt;p&gt;I am building an&lt;a href="https://myaivideomaker.com" rel="noopener noreferrer"&gt; AI video&lt;/a&gt; product with Next.js, and recently I needed a scalable way to publish creative video concepts and trending formats.&lt;/p&gt;

&lt;p&gt;The obvious solution was to create an &lt;code&gt;/ideas/&lt;/code&gt; page.&lt;/p&gt;

&lt;p&gt;My first implementation, however, was not a good one.&lt;/p&gt;

&lt;p&gt;I filled the page with a large grid of prompts, examples, categories, and visual cards. It looked busy, but it did not provide a clear information architecture. More importantly, most of those ideas did not yet have their own pages.&lt;/p&gt;

&lt;p&gt;I eventually replaced that design with a much simpler content hub.&lt;/p&gt;

&lt;p&gt;This post explains the structure I chose, the SEO decisions behind it, and how I kept it compatible with the rest of my Next.js landing-page system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with building a large gallery too early
&lt;/h2&gt;

&lt;p&gt;A large gallery can look impressive, but it creates several problems when the content library is still small:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cards may lead to pages that do not exist.&lt;/li&gt;
&lt;li&gt;Similar descriptions can create repetitive content.&lt;/li&gt;
&lt;li&gt;The hub competes with future detail pages.&lt;/li&gt;
&lt;li&gt;Updating trends becomes difficult.&lt;/li&gt;
&lt;li&gt;The design no longer matches the rest of the website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of pretending that I already had a complete ideas library, I decided to publish one useful entry and make the structure expandable.&lt;/p&gt;

&lt;p&gt;The first featured format is “Hotel Lobby AI.” Future concepts can be added as new cards when their corresponding content is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  The page structure
&lt;/h2&gt;

&lt;p&gt;I reused the same module system as the other product pages on the site.&lt;/p&gt;

&lt;p&gt;The final structure is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A focused hero section&lt;/li&gt;
&lt;li&gt;A featured ideas section&lt;/li&gt;
&lt;li&gt;An explanation of what AI video ideas are&lt;/li&gt;
&lt;li&gt;A three-step workflow&lt;/li&gt;
&lt;li&gt;Benefits of planning the idea first&lt;/li&gt;
&lt;li&gt;Use cases&lt;/li&gt;
&lt;li&gt;A prompt-writing guide&lt;/li&gt;
&lt;li&gt;Frequently asked questions&lt;/li&gt;
&lt;li&gt;A final call to action&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Only the featured ideas section is unique to this page. Everything else uses components already available in the project.&lt;/p&gt;

&lt;p&gt;A simplified version of the content configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"page_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"introduction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"modules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"featured-ai-video-ideas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ideas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"featuredIdeas"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"what-are-ai-video-ideas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"intro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"what"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"how-to-use-ai-video-ideas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"how"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"faq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"faq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"faq"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps presentation logic in reusable components while the page content remains data-driven.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the featured idea card
&lt;/h2&gt;

&lt;p&gt;The first version of the card was too large. It looked more like a banner than an entry in a growing content library.&lt;/p&gt;

&lt;p&gt;I reduced it to a compact horizontal card containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One temporary illustration&lt;/li&gt;
&lt;li&gt;A category label&lt;/li&gt;
&lt;li&gt;The idea name&lt;/li&gt;
&lt;li&gt;A short explanation&lt;/li&gt;
&lt;li&gt;One clear action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On mobile, the same card becomes a vertical layout.&lt;/p&gt;

&lt;p&gt;The important decision was not to create fake cards simply to fill the grid. The page currently displays one idea because only one idea is ready. When another guide is complete, I can add another item to the data without redesigning the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separating the hub from future detail pages
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/ideas/&lt;/code&gt; route targets the broad intent behind “AI video ideas.”&lt;/p&gt;

&lt;p&gt;Future trend pages can target narrower searches, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ideas/hotel-lobby-ai/
/ideas/ai-product-video-ideas/
/ideas/ai-video-ideas-for-youtube/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each child page should have a distinct purpose. It should not repeat the hub description with a different title.&lt;/p&gt;

&lt;p&gt;A detail page should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clear explanation of the format&lt;/li&gt;
&lt;li&gt;Required inputs&lt;/li&gt;
&lt;li&gt;A practical workflow&lt;/li&gt;
&lt;li&gt;Prompt examples&lt;/li&gt;
&lt;li&gt;Limitations&lt;/li&gt;
&lt;li&gt;Usage and permission considerations&lt;/li&gt;
&lt;li&gt;A relevant generator link&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until a detail page exists, the hub should not link to an empty or placeholder URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metadata and canonical URLs
&lt;/h2&gt;

&lt;p&gt;The Ideas page has its own title, description, canonical URL, and social metadata.&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;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AI Video Ideas &amp;amp; Creative Prompts | MyAIVideo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Find practical AI video ideas for YouTube, TikTok, Reels, marketing and storytelling.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://myaivideomaker.com/ideas/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;robots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;index&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="na"&gt;follow&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also avoided publishing alternate-language URLs before translated versions exist.&lt;/p&gt;

&lt;p&gt;Unsupported locale routes redirect to the English page instead of returning duplicated English content under multiple locale paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Structured data
&lt;/h2&gt;

&lt;p&gt;The page uses structured data that matches its visible content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CollectionPage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ItemList&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BreadcrumbList&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FAQPage&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;ItemList&lt;/code&gt; currently contains one entry. Its item count will grow only when new visible idea cards are added.&lt;/p&gt;

&lt;p&gt;This is preferable to declaring items or pages that visitors cannot actually access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sitemap and internal links
&lt;/h2&gt;

&lt;p&gt;The new URL was added to the sitemap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://myaivideomaker.com/ideas/&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;changefreq&amp;gt;&lt;/span&gt;weekly&lt;span class="nt"&gt;&amp;lt;/changefreq&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.8&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also added contextual links from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The main navigation&lt;/li&gt;
&lt;li&gt;The homepage&lt;/li&gt;
&lt;li&gt;The footer&lt;/li&gt;
&lt;li&gt;The text-to-video page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows visitors and crawlers to discover the hub without relying only on the sitemap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The main lesson was simple: a content hub should reflect the content that actually exists.&lt;/p&gt;

&lt;p&gt;A smaller page with one real entry is better than a large gallery of artificial placeholders.&lt;/p&gt;

&lt;p&gt;The reusable module approach also makes future expansion safer. I can add new trend pages and cards without changing the overall layout, metadata logic, or responsive behavior.&lt;/p&gt;

&lt;p&gt;If you want to inspect the current implementation, here is the &lt;a href="https://myaivideomaker.com/ideas/" rel="noopener noreferrer"&gt;AI Video Ideas hub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What approach do&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>ai</category>
      <category>seo</category>
    </item>
    <item>
      <title>I Built a Free Birth Chart Calculator with Live Transit Tracking</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Thu, 03 Sep 2026 04:27:14 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/i-built-a-free-birth-chart-calculator-with-live-transit-tracking-1of</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/i-built-a-free-birth-chart-calculator-with-live-transit-tracking-1of</guid>
      <description>&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Most free astrology sites give you a one-time chart reading and stop there. &lt;br&gt;
I wanted something that treats your natal chart as a starting point, &lt;br&gt;
then keeps showing you how &lt;em&gt;today's&lt;/em&gt; sky connects to it — without &lt;br&gt;
requiring signup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbeoba2dx5l424ikyz2ac.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbeoba2dx5l424ikyz2ac.png" alt=" " width="799" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Generates a full natal chart from birth date, time, and place 
(Western Tropical, Whole Sign houses)&lt;/li&gt;
&lt;li&gt;Shows your Sun/Moon/Rising ("Big Three") first, before the deeper 
technical layers&lt;/li&gt;
&lt;li&gt;Tracks personal transits: what's active now, this week, and the 
next 30 days&lt;/li&gt;
&lt;li&gt;Gracefully handles unknown birth times — hides house/Rising results 
instead of guessing&lt;/li&gt;
&lt;li&gt;No account needed — charts are saved locally in the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Some technical notes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzixe188yclr0yt2tppk4.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzixe188yclr0yt2tppk4.png" alt=" " width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chart calculations are deterministic (ephemeris-based), kept 
separate from any generated explanation text&lt;/li&gt;
&lt;li&gt;Saved charts live in browser storage, not a server, by default&lt;/li&gt;
&lt;li&gt;Built mobile-first with an interactive SVG chart wheel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it here 👉 [&lt;a href="https://annorasky.com" rel="noopener noreferrer"&gt;annorasky&lt;/a&gt;.com]&lt;/p&gt;

&lt;p&gt;Would love feedback from other devs, especially on the chart &lt;br&gt;
wheel UX or performance on mobile.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built an AI Landscape Design Tool with Next.js + Cloudflare</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Sun, 02 Aug 2026 01:16:09 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/i-built-an-ai-landscape-design-tool-with-nextjs-cloudflare-1oo0</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/i-built-an-ai-landscape-design-tool-with-nextjs-cloudflare-1oo0</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foh7qi4adr6tgos9l3ofk.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foh7qi4adr6tgos9l3ofk.png" alt=" " width="799" height="418"&gt;&lt;/a&gt;&lt;br&gt;
I wanted a practical answer to one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I preview a yard makeover from a real photo before spending money?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Generic &lt;a href="https://landscapedesignai.net" rel="noopener noreferrer"&gt;AI image tools&lt;/a&gt; make pretty gardens. They are worse at keeping your actual house, fence, and proportions. So I built &lt;strong&gt;LandscapeAI&lt;/strong&gt; — upload one outdoor photo, pick a style, generate a design concept.&lt;/p&gt;

&lt;p&gt;Live app: &lt;a href="https://landscapedesignai.net/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=landscapeai_short" rel="noopener noreferrer"&gt;landscapedesignai.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a short build log: stack, architecture, and the bugs that mattered.&lt;/p&gt;
&lt;h2&gt;
  
  
  The product
&lt;/h2&gt;

&lt;p&gt;Users upload a front yard, backyard, garden, patio, or balcony photo. They choose a style (modern, cottage, zen, Mediterranean, xeriscape…), add optional goals like privacy or low maintenance, then generate a concept.&lt;/p&gt;

&lt;p&gt;New users get a small free starter allowance. Extra runs use credits sold as one-time packs.&lt;/p&gt;

&lt;p&gt;Clear boundary helped a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good for early visualization and style comparison&lt;/li&gt;
&lt;li&gt;Not construction drawings, permits, or guaranteed plant lists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Narrow scope made the engineering shippable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;App&lt;/td&gt;
&lt;td&gt;Next.js 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;OpenNext + Cloudflare Workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB / Storage&lt;/td&gt;
&lt;td&gt;D1 + R2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;NextAuth (Google)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payments&lt;/td&gt;
&lt;td&gt;Creem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image gen&lt;/td&gt;
&lt;td&gt;kie → fal → Replicate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cloudflare end-to-end kept infra simple for an early AI SaaS: compute, database, storage, DNS in one place.&lt;/p&gt;
&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Everything goes through one gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload photo + prompt
  → auth + credit check
  → content moderation (fail closed)
  → provider router (kie / fal / replicate)
  → save result to R2
  → decrease credits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One route for generation means credits, safety, failover, and logging stay in one place. I recommend this for any AI image product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three lessons from production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Multi-provider fallback is worth it
&lt;/h3&gt;

&lt;p&gt;AI image APIs fail, rate-limit, or run dry. A tiny router saved the product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;primary: kie&lt;/li&gt;
&lt;li&gt;fallback: fal&lt;/li&gt;
&lt;li&gt;third: Replicate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users care that Generate works, not which vendor won.&lt;/p&gt;

&lt;p&gt;Also: some models reject data URLs for reference images. They want public URLs. I upload the user photo first, then pass the returned URL into generation. If you are doing photo-to-image editing, assume that early.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Credits beat “unlimited”
&lt;/h3&gt;

&lt;p&gt;Image generation has real unit cost. LandscapeAI uses starter credits + one-time packs instead of forcing subscriptions first.&lt;/p&gt;

&lt;p&gt;Yard redesign is often a short project. People try a few styles, then stop. One-time packs match that better than a monthly plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AI payments need compliance, not just checkout
&lt;/h3&gt;

&lt;p&gt;For AI image products, payment providers may require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visible paid pricing before purchase&lt;/li&gt;
&lt;li&gt;consistent support email&lt;/li&gt;
&lt;li&gt;explicit NSFW prohibition in Terms / AUP&lt;/li&gt;
&lt;li&gt;moderation on every prompt before generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My rule: &lt;strong&gt;no prompt reaches the model without a moderation decision&lt;/strong&gt;. Block on &lt;code&gt;deny&lt;/code&gt;/&lt;code&gt;flag&lt;/code&gt;, and fail closed if moderation is down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that looked like “AI is broken”
&lt;/h2&gt;

&lt;p&gt;One production error was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload failed: 400 Bad Request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generation had already succeeded. R2 upload failed because production storage config/secrets were wrong. From the user’s view, the whole feature was dead.&lt;/p&gt;

&lt;p&gt;For AI apps, success means all three:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;model success&lt;/li&gt;
&lt;li&gt;storage success&lt;/li&gt;
&lt;li&gt;credit ledger success&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Log them separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’d do earlier next time
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Build the generation gateway first (credits + moderation + providers)&lt;/li&gt;
&lt;li&gt;Treat storage secrets as launch-critical, not optional&lt;/li&gt;
&lt;li&gt;Show pricing packs before payments are fully polished&lt;/li&gt;
&lt;li&gt;Log moderation success, not only failures&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;a href="https://landscapedesignai.net/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=landscapeai_short" rel="noopener noreferrer"&gt;Generate an AI landscape design from your yard photo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sign in, upload one outdoor photo, and use the free starter designs.&lt;/p&gt;

&lt;p&gt;If you are building a similar AI SaaS, comment with your stack — happy to compare notes on provider fallback, Cloudflare deploys, or credit packaging.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>As a fellow tab hoarder, I use tools to finish projects so I can finally close them! I create graphics with https://graffiti-generator.org, edit images via https://fireredimage.org, and make academic figures with https://paper-banana.app. For a fun break,</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Wed, 15 Apr 2026 09:16:30 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/as-a-fellow-tab-hoarder-i-use-tools-to-finish-projects-so-i-can-finally-close-them-i-create-beb</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/as-a-fellow-tab-hoarder-i-use-tools-to-finish-projects-so-i-can-finally-close-them-i-create-beb</guid>
      <description>&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://graffiti-generator.org" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;graffiti-generator.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://fireredimage.org" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;fireredimage.org&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://paper-banana.app" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;paper-banana.app&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>I Built a Free Astrocartography Calculator with Next.js — Here's the Full Story</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Sat, 28 Mar 2026 02:47:58 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/i-built-a-free-astrocartography-calculator-with-nextjs-heres-the-full-story-4fbd</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/i-built-a-free-astrocartography-calculator-with-nextjs-heres-the-full-story-4fbd</guid>
      <description>&lt;p&gt;Most astrocartography tools online are either locked behind a $20/month paywall, built on Flash-era technology, or so cluttered with ads you can barely find the actual calculator. As a developer who stumbled into the world of astrology charts, I kept thinking: &lt;em&gt;someone should just build a clean, free one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I did. Here's the honest story of how I built &lt;strong&gt;&lt;a href="https://astrocarto.org" rel="noopener noreferrer"&gt;Astrocarto&lt;/a&gt;&lt;/strong&gt; — the technical decisions, the unexpected hard parts, and what I'd do differently.&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%2Fdec42kamnu8dsxvw3kbq.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%2Fdec42kamnu8dsxvw3kbq.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Even Is Astrocartography?
&lt;/h2&gt;

&lt;p&gt;Quick background for the non-astrology crowd (which is probably most of you):&lt;/p&gt;

&lt;p&gt;Astrocartography is a technique that takes the positions of planets at the exact moment you were born and projects them as lines onto a world map. The idea is that certain locations carry specific planetary energies for you — your "Venus line" might run through Paris, your "Mars line" through somewhere you'd rather avoid.&lt;/p&gt;

&lt;p&gt;Whether or not you believe any of that — the &lt;em&gt;math&lt;/em&gt; is genuinely fascinating. We're talking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculating precise planetary positions using historical ephemeris data&lt;/li&gt;
&lt;li&gt;Transforming between multiple astronomical coordinate systems&lt;/li&gt;
&lt;li&gt;Projecting those coordinates onto a 2D geographic map&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a developer, I was hooked on the problem before I even thought about the product.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Framework:        Next.js 14 (App Router)
Styling:          Tailwind CSS
Map Rendering:    Leaflet + React-Leaflet
Astro Calc:       astronomia (npm)
Deployment:       Vercel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll explain the key choices below.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Next.js?
&lt;/h2&gt;

&lt;p&gt;Three reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. SEO is everything for a niche tool&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;People actively search for "free astrocartography calculator" — I needed server-side rendering so those pages could rank. With Next.js App Router, my calculator pages are fully rendered on the server before they hit the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Route Handlers for calculation logic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't want heavy astronomical math running on the client. Next.js Route Handlers let me keep that on the server cleanly:&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="c1"&gt;// app/api/calculate/route.js&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;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;nodejs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// critical — don't use Edge here&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;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;birthDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lng&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;request&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;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateNatalChart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lng&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&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="nx"&gt;chart&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;&lt;strong&gt;3. Zero-config Vercel deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Push to main → live in 30 seconds. For an indie project, this matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hardest Part: Astronomical Calculations
&lt;/h2&gt;

&lt;p&gt;I expected the map to be the hard part. I was wrong. The calculations nearly broke me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the &lt;code&gt;astronomia&lt;/code&gt; library
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;astronomia&lt;/code&gt; is a JavaScript port of Jean Meeus's &lt;em&gt;Astronomical Algorithms&lt;/em&gt; — the gold standard reference for this kind of math. It handles the heavy lifting of planetary position calculations.&lt;/p&gt;

&lt;p&gt;Here's a simplified version of how I get a planet's ecliptic longitude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;solar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;julian&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&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;astronomia&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getPlanetPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;birthDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Convert birth datetime to Julian Day Number&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateStr&lt;/span&gt; &lt;span class="o"&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;birthDate&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;T&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;birthTime&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:00Z`&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;jd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;julian&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateToJD&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;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateStr&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Get Sun's position (as example)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sunPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;solar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apparentLongitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Convert from radians to degrees&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sunPos&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;360&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;longitude&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;
  
  
  The coordinate system nightmare
&lt;/h3&gt;

&lt;p&gt;Where it got painful: astrocartography lines require converting between &lt;em&gt;three&lt;/em&gt; coordinate systems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ecliptic coordinates&lt;/strong&gt; — where planets are relative to Earth's orbital plane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Equatorial coordinates&lt;/strong&gt; — right ascension + declination (like lat/lng for space)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geographic coordinates&lt;/strong&gt; — actual latitude/longitude on Earth's surface&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For each planet, I need to find every latitude on Earth where that planet was on the Ascendant, Descendant, Midheaven, or IC at the moment of birth. That means iterating through latitudes and solving for the longitude where the condition holds.&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;function&lt;/span&gt; &lt;span class="nf"&gt;findAscendantLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jd&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;points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="c1"&gt;// Step through every latitude&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Binary search for the longitude where this planet rises&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;solveForAscendant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lng&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="nx"&gt;points&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;Getting this right took me about two weeks of head-scratching, cross-referencing with known charts, and fixing edge cases near the poles.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rendering the Map with Leaflet
&lt;/h2&gt;

&lt;p&gt;Once I had the coordinate arrays, rendering them with Leaflet was comparatively straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MapContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TileLayer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Polyline&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;react-leaflet&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AstroMap&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;planetLines&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;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MapContainer&lt;/span&gt;
      &lt;span class="nx"&gt;center&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[&lt;/span&gt;&lt;span class="mi"&gt;20&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="nx"&gt;zoom&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;600px&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TileLayer&lt;/span&gt;
        &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;attribution&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;© OpenStreetMap contributors&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planetLines&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;planet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;color&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Polyline&lt;/span&gt;
          &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;planet&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;positions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;pathOptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;))}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/MapContainer&lt;/span&gt;&lt;span class="err"&gt;&amp;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;One Leaflet gotcha with Next.js: Leaflet touches &lt;code&gt;window&lt;/code&gt; on import, which breaks SSR. Fix:&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="c1"&gt;// Use dynamic import with ssr: false&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AstroMap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;dynamic&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;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/AstroMap&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;ssr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;loading&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;map-skeleton&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  What Astrocarto Can Do Right Now
&lt;/h2&gt;

&lt;p&gt;The tool currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Rising Sign Calculator&lt;/strong&gt; — your ascendant based on exact birth time + location&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Natal Chart&lt;/strong&gt; — full planetary positions at birth&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Transit Chart&lt;/strong&gt; — current planetary positions overlaid on your natal chart&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Relocation Chart&lt;/strong&gt; — how your chart shifts if you move cities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All free. No account required. Just enter your birth details and go.&lt;/p&gt;

&lt;p&gt;Try it: &lt;strong&gt;&lt;a href="https://astrocarto.org" rel="noopener noreferrer"&gt;astrocarto.org&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;1. Validate calculations against existing tools first&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I spent days debugging math that turned out to be correct — I just didn't have a reliable reference to check against. Next time I'd set up automated comparison tests against established chart software from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Build the map UI before perfecting the calculations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Seeing lines on a map, even approximate ones, keeps you motivated. I got too deep into calculation accuracy before I had anything visual to show for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add caching earlier&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Astronomical calculations for the same birth data always return the same result. I should have added a simple cache from the start rather than recalculating on every request.&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="c1"&gt;// Simple in-memory cache I added later&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cache&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;Map&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;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="nx"&gt;body&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;request&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;cacheKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&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="nx"&gt;Response&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="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateNatalChart&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="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&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="nx"&gt;result&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;
  
  
  What's Next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Mobile-optimized map gestures&lt;/li&gt;
&lt;li&gt;[ ] Shareable chart URLs (so you can send your chart to a friend)&lt;/li&gt;
&lt;li&gt;[ ] PDF export&lt;/li&gt;
&lt;li&gt;[ ] More celestial bodies — asteroids, Chiron, nodes&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Building niche tools is genuinely one of my favorite things to do as an indie developer. The intersection of an obscure domain and a real technical problem is where the most interesting projects live.&lt;/p&gt;

&lt;p&gt;If you've built something in a weird niche — astrology, birdwatching, chess, anything — I'd love to hear about it in the comments.&lt;/p&gt;

&lt;p&gt;And if you're curious about your own astrocartography: &lt;strong&gt;&lt;a href="https://astrocarto.org" rel="noopener noreferrer"&gt;astrocarto.org&lt;/a&gt;&lt;/strong&gt; — takes about 60 seconds.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>indiedev</category>
    </item>
    <item>
      <title>I Got Obsessed with LinkedIn Pinpoint, Until I Found This Tool</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Tue, 21 Oct 2025 11:11:53 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/i-got-obsessed-with-linkedin-pinpoint-until-i-found-this-tool-17m2</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/i-got-obsessed-with-linkedin-pinpoint-until-i-found-this-tool-17m2</guid>
      <description>&lt;p&gt;Get today's LinkedIn Pinpoint answer instantly! &lt;br&gt;
&lt;a href="https://pinpointanswer.net" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;br&gt;
Updated daily with verified solutions, detailed clue explanations, and strategies to maintain your winning streak. Free access to current and historical puzzle answers.&lt;/p&gt;

</description>
      <category>pinpoint</category>
    </item>
    <item>
      <title>wplaceart</title>
      <dc:creator>冠</dc:creator>
      <pubDate>Sat, 23 Aug 2025 09:43:59 +0000</pubDate>
      <link>https://dev.to/_eb3468ba11d30aaf81a0e85/wplaceart-2f8o</link>
      <guid>https://dev.to/_eb3468ba11d30aaf81a0e85/wplaceart-2f8o</guid>
      <description>&lt;p&gt;Welcome to &lt;a href="https://wplaceart.com" rel="noopener noreferrer"&gt;Wplace Art&lt;/a&gt;, your premier destination for transforming ordinary images into extraordinary pixel art masterpieces. Our powerful Wplace Pixel converter combines cutting-edge technology with intuitive design, making it easy to create stunning retro-style artwork perfect for games, social media, and personal projects.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
