<?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: Cenk KURTOĞLU</title>
    <description>The latest articles on DEV Community by Cenk KURTOĞLU (@cekuu35).</description>
    <link>https://dev.to/cekuu35</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%2F3995356%2Ff0bdbda0-d9ea-46f9-aa42-626005189d37.png</url>
      <title>DEV Community: Cenk KURTOĞLU</title>
      <link>https://dev.to/cekuu35</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cekuu35"/>
    <language>en</language>
    <item>
      <title>I built an API-callable llms.txt generator after Lighthouse added an audit</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Sat, 04 Jul 2026 08:44:45 +0000</pubDate>
      <link>https://dev.to/cekuu35/i-built-a-batch-llmstxt-generator-make-any-site-ai-readable-at-scale-1h72</link>
      <guid>https://dev.to/cekuu35/i-built-a-batch-llmstxt-generator-make-any-site-ai-readable-at-scale-1h72</guid>
      <description>&lt;h2&gt;
  
  
  Lighthouse made &lt;code&gt;llms.txt&lt;/code&gt; harder to ignore
&lt;/h2&gt;

&lt;p&gt;In May 2026, Chrome Lighthouse documented an optional &lt;code&gt;llms.txt&lt;/code&gt; check in its new Agentic Browsing category.&lt;/p&gt;

&lt;p&gt;That does &lt;strong&gt;not&lt;/strong&gt; mean &lt;code&gt;llms.txt&lt;/code&gt; is a guaranteed ranking factor. Lighthouse currently marks a missing file as N/A and flags server errors when it tries to retrieve one. The practical signal is narrower: machine-readable site summaries are becoming part of the tooling used to evaluate an agent-friendly web.&lt;/p&gt;

&lt;p&gt;The file itself is simple Markdown at the root of a domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Example Product&lt;/span&gt;
&lt;span class="gt"&gt;
&amp;gt; One sentence explaining what the product does.&lt;/span&gt;

&lt;span class="gu"&gt;## Pages&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Documentation&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/docs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Setup and API guides.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Pricing&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://example.com/pricing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: Plans and limits.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The companion &lt;code&gt;llms-full.txt&lt;/code&gt; can include the full text of the selected pages for RAG and content-ingestion workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The repetitive part was the reason I automated it
&lt;/h2&gt;

&lt;p&gt;Creating one file by hand is easy. Repeating the work across client sites, documentation portals, or scheduled content snapshots is not.&lt;/p&gt;

&lt;p&gt;So I built an Apify Actor that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;crawls same-domain pages up to a limit you choose;&lt;/li&gt;
&lt;li&gt;prefers a site's sitemap when available;&lt;/li&gt;
&lt;li&gt;extracts page titles, descriptions, and main content;&lt;/li&gt;
&lt;li&gt;outputs both &lt;code&gt;llms.txt&lt;/code&gt; and &lt;code&gt;llms-full.txt&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;stores a page-level dataset that can be exported as JSON or CSV;&lt;/li&gt;
&lt;li&gt;runs from the Apify UI, API, schedules, or automation workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it here: &lt;strong&gt;&lt;a href="https://apify.com/nacred_corner/llms-txt-generator" rel="noopener noreferrer"&gt;llms.txt Generator on Apify&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Example input:&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;"websiteUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"includeFullText"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;When the run finishes, download the generated files from the run's key-value store and review them before publishing at your domain root.&lt;/p&gt;

&lt;h2&gt;
  
  
  When this is useful
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;You manage several client sites and want the same repeatable workflow for each.&lt;/li&gt;
&lt;li&gt;You want a clean text snapshot for a RAG pipeline.&lt;/li&gt;
&lt;li&gt;You want to regenerate files on a schedule as documentation changes.&lt;/li&gt;
&lt;li&gt;You need a page inventory alongside the generated files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project is intentionally small and open source. If you try it on a site that exposes an edge case, open an issue or leave a comment with the URL pattern and expected output.&lt;/p&gt;

&lt;p&gt;Source: &lt;strong&gt;&lt;a href="https://github.com/cekuu35/llms-txt-generator" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reference: &lt;strong&gt;&lt;a href="https://developer.chrome.com/docs/lighthouse/agentic-browsing/llms-txt" rel="noopener noreferrer"&gt;Chrome Lighthouse llms.txt audit documentation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>seo</category>
      <category>opensource</category>
    </item>
    <item>
      <title>If Stripe is not available in your country, check this before rebuilding your SaaS billing</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:45:14 +0000</pubDate>
      <link>https://dev.to/cekuu35/if-stripe-is-not-available-in-your-country-check-this-before-rebuilding-your-saas-billing-4702</link>
      <guid>https://dev.to/cekuu35/if-stripe-is-not-available-in-your-country-check-this-before-rebuilding-your-saas-billing-4702</guid>
      <description>&lt;p&gt;If Stripe is not available in your country, the tempting move is to rebuild the entire billing system around the first alternative you find.&lt;/p&gt;

&lt;p&gt;That is usually too much work too early.&lt;/p&gt;

&lt;p&gt;Before replacing everything, I would check the revenue path as a system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pricing page
  -&amp;gt; checkout provider
  -&amp;gt; payment confirmation
  -&amp;gt; webhook handler
  -&amp;gt; user / organization mapping
  -&amp;gt; entitlement unlock
  -&amp;gt; failed payment recovery
  -&amp;gt; customer support fallback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provider matters, but the handoffs matter more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common mistake
&lt;/h2&gt;

&lt;p&gt;Founders compare payment providers like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stripe supports my country: yes/no&lt;/li&gt;
&lt;li&gt;fees: low/high&lt;/li&gt;
&lt;li&gt;subscriptions: yes/no&lt;/li&gt;
&lt;li&gt;checkout page: yes/no&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is useful, but incomplete.&lt;/p&gt;

&lt;p&gt;The bigger question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;After someone pays, does the correct account get the correct access every time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is not proven, you do not have billing. You have a checkout demo.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would audit first
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Pricing CTA
&lt;/h3&gt;

&lt;p&gt;Can a visitor clearly choose a plan and understand what happens next?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Checkout ownership
&lt;/h3&gt;

&lt;p&gt;Are you using a payment gateway, merchant of record, invoice flow, or manual payment path?&lt;/p&gt;

&lt;p&gt;Each one changes taxes, refunds, support, customer communication, and account recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Webhook idempotency
&lt;/h3&gt;

&lt;p&gt;Can the same payment event arrive twice without double-unlocking, double-emailing, or corrupting access state?&lt;/p&gt;

&lt;h3&gt;
  
  
  4. User mapping
&lt;/h3&gt;

&lt;p&gt;Does payment map to the right &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;organizationId&lt;/code&gt;, or workspace?&lt;/p&gt;

&lt;p&gt;This gets messy fast in B2B SaaS.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Access unlock
&lt;/h3&gt;

&lt;p&gt;Where is the source of truth for paid access?&lt;/p&gt;

&lt;p&gt;Do not scatter this across frontend checks, local storage, and one-off database fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Failure and recovery
&lt;/h3&gt;

&lt;p&gt;What happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checkout succeeds but the webhook is delayed&lt;/li&gt;
&lt;li&gt;the customer closes the tab&lt;/li&gt;
&lt;li&gt;the payment provider retries&lt;/li&gt;
&lt;li&gt;the email address differs from the app account&lt;/li&gt;
&lt;li&gt;the subscription is canceled or refunded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where most early billing systems break.&lt;/p&gt;

&lt;h2&gt;
  
  
  Polar, Lemon Squeezy, Paddle, manual invoice, or Stripe?
&lt;/h2&gt;

&lt;p&gt;The right answer depends on your country, product type, tax burden, buyer expectations, and whether you need subscriptions, one-time payments, licenses, or usage credits.&lt;/p&gt;

&lt;p&gt;But the implementation checklist is mostly the same:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one clear pricing path&lt;/li&gt;
&lt;li&gt;one checkout provider per offer&lt;/li&gt;
&lt;li&gt;one webhook handler strategy&lt;/li&gt;
&lt;li&gt;one source of truth for entitlement&lt;/li&gt;
&lt;li&gt;one support fallback when automation fails&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  I am reviewing 5 flows today
&lt;/h2&gt;

&lt;p&gt;If you are building a Next.js SaaS and payment access is the blocker, drop your URL in the comments or email it to &lt;a href="mailto:cengokurtoglu35@gmail.com"&gt;cengokurtoglu35@gmail.com&lt;/a&gt; with the subject &lt;code&gt;Free mini-audit request&lt;/code&gt;. I will reply with 2 concrete issues I would check first.&lt;/p&gt;

&lt;p&gt;I also packaged the deeper review as a small USD 49 audit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Related checklist:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/cekuu35/how-i-wire-polar-mor-payments-in-a-nextjs-saas-without-stripe-1pb0"&gt;https://dev.to/cekuu35/how-i-wire-polar-mor-payments-in-a-nextjs-saas-without-stripe-1pb0&lt;/a&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>saas</category>
      <category>payments</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I will review 5 Next.js SaaS landing/payment flows today</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:37:40 +0000</pubDate>
      <link>https://dev.to/cekuu35/i-will-review-5-nextjs-saas-landingpayment-flows-today-cgb</link>
      <guid>https://dev.to/cekuu35/i-will-review-5-nextjs-saas-landingpayment-flows-today-cgb</guid>
      <description>&lt;p&gt;Most early SaaS launches do not fail because the app has no features.&lt;/p&gt;

&lt;p&gt;They fail because the first-revenue path is not clear enough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the landing page does not explain the outcome&lt;/li&gt;
&lt;li&gt;the CTA points to a weak next step&lt;/li&gt;
&lt;li&gt;pricing is hidden or confusing&lt;/li&gt;
&lt;li&gt;checkout is disconnected from the product promise&lt;/li&gt;
&lt;li&gt;webhooks exist, but access unlock is not tested&lt;/li&gt;
&lt;li&gt;failed payments and recovery paths are ignored&lt;/li&gt;
&lt;li&gt;the deployed app has small broken states that scare buyers away&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am reviewing 5 Next.js SaaS landing/payment flows today.&lt;/p&gt;

&lt;p&gt;If you are building with Next.js, Stripe, Polar, Lemon Squeezy, or a SaaS boilerplate, drop your URL in the comments and I will reply with 2 concrete issues I would fix first.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I will look at
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Landing page clarity
&lt;/h3&gt;

&lt;p&gt;Can a new visitor understand who the product is for, what pain it removes, and what happens after clicking the CTA?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Pricing path
&lt;/h3&gt;

&lt;p&gt;Is there a visible route from interest to payment, or does the user need to guess how to buy?&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Checkout handoff
&lt;/h3&gt;

&lt;p&gt;Does checkout match the plan, product, and promise on the page?&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Webhook and access unlock
&lt;/h3&gt;

&lt;p&gt;After payment, does the right user or organization get the right access without manual work?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Recovery paths
&lt;/h3&gt;

&lt;p&gt;What happens when payment fails, a webhook retries, an email is missed, or a user comes back later?&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want the deeper version
&lt;/h2&gt;

&lt;p&gt;I also packaged this into a small paid audit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It covers landing CTA, pricing, checkout, webhook/access unlock, deployment blockers, and the exact implementation path.&lt;/p&gt;

&lt;p&gt;I wrote the payment checklist I use here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/cekuu35/how-i-wire-polar-mor-payments-in-a-nextjs-saas-without-stripe-1pb0"&gt;https://dev.to/cekuu35/how-i-wire-polar-mor-payments-in-a-nextjs-saas-without-stripe-1pb0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Drop a URL if you want the free 2-issue version. If you do not want to post publicly, email the URL to &lt;a href="mailto:cengokurtoglu35@gmail.com"&gt;cengokurtoglu35@gmail.com&lt;/a&gt; with the subject &lt;code&gt;Free mini-audit request&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>saas</category>
      <category>webdev</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>What 'Production-Ready' Actually Means for a Next.js Template (a Checklist)</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:31:32 +0000</pubDate>
      <link>https://dev.to/cekuu35/what-production-ready-actually-means-for-a-nextjs-template-a-checklist-25eg</link>
      <guid>https://dev.to/cekuu35/what-production-ready-actually-means-for-a-nextjs-template-a-checklist-25eg</guid>
      <description>&lt;p&gt;"Production-ready" is the most overused phrase in template marketplaces. After building 20 Next.js templates to a strict bar, here's the checklist I actually use to decide whether something deserves the label.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. More than one page
&lt;/h2&gt;

&lt;p&gt;A landing page is not a website. A production template has real routes — home, features/services, pricing, a blog index with dynamic posts, and contact. If the "template" is one &lt;code&gt;page.tsx&lt;/code&gt;, you're buying a screenshot.&lt;/p&gt;

&lt;p&gt;✅ At least 5 routes&lt;br&gt;
✅ Shared &lt;code&gt;layout.tsx&lt;/code&gt; with nav + footer&lt;br&gt;
✅ Active-link state so users know where they are&lt;/p&gt;
&lt;h2&gt;
  
  
  2. No dead buttons
&lt;/h2&gt;

&lt;p&gt;Open the demo and click everything. A "Sign up" button that goes nowhere or a nav link that 404s tells you the rest of the code is just as unfinished. Every interactive element should either do something real or be honestly labeled as a demo.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Forms that submit
&lt;/h2&gt;

&lt;p&gt;A contact form needs client-side validation, a visible success/error state, and a backend it actually posts to (a Next.js &lt;code&gt;/api&lt;/code&gt; route handler is enough). Bonus points if the template documents how to wire it to a real email provider.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/contact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. SEO that isn't an afterthought
&lt;/h2&gt;

&lt;p&gt;App Router makes this cheap — there's no excuse to skip it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;generateMetadata&lt;/code&gt; per route (title, description, canonical)&lt;/li&gt;
&lt;li&gt;Open Graph + Twitter card tags so shared links render a preview&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sitemap.ts&lt;/code&gt; and &lt;code&gt;robots.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Structured data (JSON-LD) for the org/product where it fits&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. It builds clean
&lt;/h2&gt;

&lt;p&gt;The bar is simple and non-negotiable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tsc &lt;span class="nt"&gt;--noEmit&lt;/span&gt;   &lt;span class="c"&gt;# 0 errors&lt;/span&gt;
next build     &lt;span class="c"&gt;# 0 errors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a template ships with type errors or build warnings, every change you make is gambling. Strict TypeScript isn't gatekeeping — it's the thing that lets a buyer edit confidently.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. One-file customization
&lt;/h2&gt;

&lt;p&gt;You should be able to rebrand without reading the component tree. A single &lt;code&gt;data.ts&lt;/code&gt; / &lt;code&gt;content.ts&lt;/code&gt; config holds the name, colors, nav, copy, and pricing. If you have to grep for hardcoded strings across 40 files, it's not a template — it's homework.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Honest demo behavior
&lt;/h2&gt;

&lt;p&gt;This is the one most templates get wrong. If a template shows a checkout, it should say "demo checkout — no real payment." A crypto template should say "not investment advice." Pretending to transact is how you generate refund requests and chargebacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;Production-ready?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 page only&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dead buttons&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forms post to a real route&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-route SEO metadata&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;tsc&lt;/code&gt; + &lt;code&gt;next build&lt;/code&gt; clean&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rebrand from one file&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Honest demo labels&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I held all 20 of my templates to this list. You can pressure-test them yourself — they're live (&lt;a href="https://07-ecommerce-store.vercel.app" rel="noopener noreferrer"&gt;e-commerce&lt;/a&gt;, &lt;a href="https://08-online-course.vercel.app" rel="noopener noreferrer"&gt;online course&lt;/a&gt;, &lt;a href="https://17-hotel-booking.vercel.app" rel="noopener noreferrer"&gt;hotel booking&lt;/a&gt;) — and the source is on &lt;a href="https://cenkkurtoglu.com/templates" rel="noopener noreferrer"&gt;my site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want me to audit your own Next.js launch path, I offer a focused review of landing CTA, payment provider, checkout, webhook/access unlock, and production blockers: &lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Direct template checkout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20-template bundle: &lt;a href="https://cengokurtoglu.gumroad.com/l/vuhstz" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/vuhstz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI startup template: &lt;a href="https://cengokurtoglu.gumroad.com/l/mmpgds" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/mmpgds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modern SaaS template: &lt;a href="https://cengokurtoglu.gumroad.com/l/butotw" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/butotw&lt;/a&gt;
What's on your "production-ready" checklist that I left off?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>tailwindcss</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Ship a Client Website in a Day: The Single-Config Pattern for Next.js</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:30:56 +0000</pubDate>
      <link>https://dev.to/cekuu35/ship-a-client-website-in-a-day-the-single-config-pattern-for-nextjs-4g5g</link>
      <guid>https://dev.to/cekuu35/ship-a-client-website-in-a-day-the-single-config-pattern-for-nextjs-4g5g</guid>
      <description>&lt;p&gt;If you do client work, you know the time sink isn't building the site — it's rebuilding the same site for the fifth client with different colors, copy, and a different logo. The fix isn't "another framework." It's a discipline: &lt;strong&gt;drive the whole site from one config object&lt;/strong&gt; so rebranding is data entry, not engineering.&lt;/p&gt;

&lt;p&gt;Here's the pattern I use across 20 production templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. One source of truth
&lt;/h2&gt;

&lt;p&gt;Put everything brand-specific in a single typed file:&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/lib/data.ts&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;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lumen Agency&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lumen.studio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0ea5e9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#f59e0b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1rem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello@lumen.studio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+1 555 0100&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Work&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/work&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Services&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/services&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Contact&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/contact&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;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;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;Brand Identity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;desc&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;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sparkles&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;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;Web Design&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;desc&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;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Layout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it's &lt;code&gt;as const&lt;/code&gt;, TypeScript gives you autocomplete and catches typos in nav hrefs or missing fields before the build does.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Map design tokens to CSS variables
&lt;/h2&gt;

&lt;p&gt;Don't scatter hex codes through 40 components. Feed the brand colors into CSS variables once, and let Tailwind read them:&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;// in the root layout&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;body&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--brand&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--accent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accent&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// tailwind.config — tokens point at the variables&lt;/span&gt;
&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--brand)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;var(--accent)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a rebrand is two hex values, and every button, badge, and link updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Components consume data, never define it
&lt;/h2&gt;

&lt;p&gt;The anti-pattern is a &lt;code&gt;Hero&lt;/code&gt; that hardcodes "Welcome to Acme." The pattern is a &lt;code&gt;Hero&lt;/code&gt; that takes &lt;code&gt;site.name&lt;/code&gt; and &lt;code&gt;site.tagline&lt;/code&gt;. Same for nav, footer, pricing tables, and testimonials. The component owns &lt;em&gt;layout&lt;/em&gt;; the config owns &lt;em&gt;content&lt;/em&gt;.&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;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Hero&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagline&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; — &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&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;h2&gt;
  
  
  4. Keep routing conventional
&lt;/h2&gt;

&lt;p&gt;Next.js App Router already gives you multi-page structure for free — &lt;code&gt;app/services/page.tsx&lt;/code&gt;, &lt;code&gt;app/blog/[slug]/page.tsx&lt;/code&gt;. Use &lt;code&gt;generateMetadata&lt;/code&gt; per route so each page gets its own title and Open Graph tags. Clients notice when sharing a link shows a real preview card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is the freelancer's unfair advantage
&lt;/h2&gt;

&lt;p&gt;With this pattern, a new client engagement looks like: copy template → edit &lt;code&gt;data.ts&lt;/code&gt; → swap logo → &lt;code&gt;next build&lt;/code&gt; → deploy to Vercel. A day, not a week. You charge for the outcome, not the hours, and your margin goes up because the second, third, and tenth site cost you almost nothing.&lt;/p&gt;

&lt;p&gt;I built 20 templates on exactly this pattern (SaaS, agency, restaurant, real estate, e-commerce, and more). You can see them live — &lt;a href="https://03-digital-agency.vercel.app" rel="noopener noreferrer"&gt;digital agency&lt;/a&gt;, &lt;a href="https://05-real-estate.vercel.app" rel="noopener noreferrer"&gt;real estate&lt;/a&gt;, &lt;a href="https://07-ecommerce-store.vercel.app" rel="noopener noreferrer"&gt;e-commerce&lt;/a&gt; — or grab the source on &lt;a href="https://cenkkurtoglu.com/templates" rel="noopener noreferrer"&gt;my site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want a second pass before sending traffic to a client site, I also packaged a small audit for landing CTA, checkout/payment path, webhook/access unlock, and deployment blockers: &lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Direct template checkout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20-template bundle: &lt;a href="https://cengokurtoglu.gumroad.com/l/vuhstz" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/vuhstz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI startup template: &lt;a href="https://cengokurtoglu.gumroad.com/l/mmpgds" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/mmpgds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modern SaaS template: &lt;a href="https://cengokurtoglu.gumroad.com/l/butotw" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/butotw&lt;/a&gt;
Do you drive your client sites from config, or do you fork-and-edit each time? Curious what's working for other people doing volume client work.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>freelance</category>
      <category>typescript</category>
    </item>
    <item>
      <title>I Built 20 Production-Ready Next.js + Tailwind Templates — Here's the Architecture</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:30:20 +0000</pubDate>
      <link>https://dev.to/cekuu35/i-built-20-production-ready-nextjs-tailwind-templates-heres-the-architecture-27ag</link>
      <guid>https://dev.to/cekuu35/i-built-20-production-ready-nextjs-tailwind-templates-heres-the-architecture-27ag</guid>
      <description>&lt;p&gt;Most "website templates" you buy online are a single landing page with a hero, three feature cards, and a dead "Contact" button. I wanted the opposite: 20 templates that are actually shippable websites — multiple routes, working navigation, real forms, and zero broken buttons. This post is the architecture I landed on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint that shaped everything: one config file
&lt;/h2&gt;

&lt;p&gt;The single most important decision was that a buyer should never have to hunt through JSX to rebrand a template. Every template reads from &lt;strong&gt;one&lt;/strong&gt; data source — &lt;code&gt;src/lib/data.ts&lt;/code&gt; (or &lt;code&gt;content.ts&lt;/code&gt; / &lt;code&gt;siteConfig.ts&lt;/code&gt; depending on the template). Change the company name, palette, copy, nav links, and pricing in that one file, and the entire site updates.&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/lib/data.ts&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;site&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nexus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tagline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ship faster with the all-in-one platform&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Features&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/features&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Pricing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/pricing&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;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog&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;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#6366f1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#22d3ee&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="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Components consume &lt;code&gt;site&lt;/code&gt; rather than hardcoding strings. This is the difference between a template a non-developer can use and one only the author understands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack: boring on purpose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 15, App Router&lt;/strong&gt; — file-system routing, server components by default, &lt;code&gt;generateMetadata&lt;/code&gt; for per-route SEO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; — utility classes keep the design tokens in one place and make rebrands a search-and-replace, not a refactor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript, strict&lt;/strong&gt; — every template ships with &lt;code&gt;tsc --noEmit&lt;/code&gt; returning &lt;strong&gt;0 errors&lt;/strong&gt; and &lt;code&gt;next build&lt;/code&gt; returning &lt;strong&gt;0 errors&lt;/strong&gt;. That's the bar before it counts as done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No exotic state libraries, no half-integrated CMS. Boring tech means the buyer can read it on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-page from the start
&lt;/h2&gt;

&lt;p&gt;Each template has &lt;strong&gt;at least 5 routes&lt;/strong&gt; (some have 20+): home, a features/services page, pricing, a blog index with dynamic &lt;code&gt;[slug]&lt;/code&gt; posts, and a contact page. The nav and footer live in &lt;code&gt;layout.tsx&lt;/code&gt; with an active-link helper so the current page is highlighted — small detail, but it's what separates "a page" from "a site."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// active link in the shared layout&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;usePathname&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;isActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;href&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="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-brand&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;text-muted hover:text-fg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Forms that actually do something
&lt;/h2&gt;

&lt;p&gt;Every contact form has client-side validation, a success state, and posts to a real &lt;code&gt;/api/contact&lt;/code&gt; route handler. Where a template implies a transaction (an e-commerce checkout, a crypto swap), the demo behavior is &lt;strong&gt;honestly labeled&lt;/strong&gt; — e.g. "demo checkout — no real payment" or "not investment advice." A template that pretends to charge a card is a liability, not a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honesty test
&lt;/h2&gt;

&lt;p&gt;I almost shipped these as single-page landings. A blunt piece of feedback stopped me: a one-page "template" sold as a website feels a little like a scam. So the rule became: if a buyer drops their content in, they should get a &lt;strong&gt;real multi-page site&lt;/strong&gt;, not a glorified hero section. That reframed the whole project.&lt;/p&gt;

&lt;h2&gt;
  
  
  See them live
&lt;/h2&gt;

&lt;p&gt;All 20 are deployed as live demos (e.g. &lt;a href="https://01-modern-saas.vercel.app" rel="noopener noreferrer"&gt;modern SaaS&lt;/a&gt;, &lt;a href="https://02-ai-startup.vercel.app" rel="noopener noreferrer"&gt;AI startup&lt;/a&gt;, &lt;a href="https://03-digital-agency.vercel.app" rel="noopener noreferrer"&gt;digital agency&lt;/a&gt;). If you want the source, they're available as single templates or a full bundle on &lt;a href="https://cenkkurtoglu.com/templates" rel="noopener noreferrer"&gt;my site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are trying to turn a Next.js site into revenue, I also offer a focused audit of the landing, payment path, webhook/access unlock, and launch blockers: &lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Direct template checkout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20-template bundle: &lt;a href="https://cengokurtoglu.gumroad.com/l/vuhstz" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/vuhstz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI startup template: &lt;a href="https://cengokurtoglu.gumroad.com/l/mmpgds" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/mmpgds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modern SaaS template: &lt;a href="https://cengokurtoglu.gumroad.com/l/butotw" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/butotw&lt;/a&gt;
What would you add to a template before calling it "production-ready"? I'm curious where other people draw the line.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>tailwindcss</category>
      <category>typescript</category>
    </item>
    <item>
      <title>How I Wire Polar MoR Payments In A Next.js SaaS Without Stripe</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Fri, 03 Jul 2026 09:31:42 +0000</pubDate>
      <link>https://dev.to/cekuu35/how-i-wire-polar-mor-payments-in-a-nextjs-saas-without-stripe-1pb0</link>
      <guid>https://dev.to/cekuu35/how-i-wire-polar-mor-payments-in-a-nextjs-saas-without-stripe-1pb0</guid>
      <description>&lt;h1&gt;
  
  
  How I Wire Polar MoR Payments In A Next.js SaaS Without Stripe
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Hook
&lt;/h2&gt;

&lt;p&gt;Stripe is great, but it is not always available or practical for indie SaaS founders outside the US/EU.&lt;/p&gt;

&lt;p&gt;The part most founders underestimate is not the checkout button. It is the full revenue path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;product or subscription setup&lt;/li&gt;
&lt;li&gt;checkout session&lt;/li&gt;
&lt;li&gt;webhook verification&lt;/li&gt;
&lt;li&gt;user access unlock&lt;/li&gt;
&lt;li&gt;failed payment handling&lt;/li&gt;
&lt;li&gt;customer portal&lt;/li&gt;
&lt;li&gt;deployment secrets&lt;/li&gt;
&lt;li&gt;testing the happy path and failure path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one part is half-wired, you can get paid and still fail to unlock access correctly.&lt;/p&gt;

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

&lt;p&gt;For a small Next.js SaaS, my default stack is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js App Router&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;hosted auth or simple session auth depending on scope&lt;/li&gt;
&lt;li&gt;Polar as Merchant of Record&lt;/li&gt;
&lt;li&gt;webhook route for payment/subscription events&lt;/li&gt;
&lt;li&gt;database flag for entitlement/access&lt;/li&gt;
&lt;li&gt;Vercel deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is not to build a giant billing platform. The goal is to get one paid offer working end to end.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Minimum Payment Flow
&lt;/h2&gt;

&lt;p&gt;A clean first version needs this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A product or subscription in the payment provider&lt;/li&gt;
&lt;li&gt;A checkout URL or generated checkout session&lt;/li&gt;
&lt;li&gt;A user identity attached to checkout metadata&lt;/li&gt;
&lt;li&gt;A webhook endpoint that verifies the event&lt;/li&gt;
&lt;li&gt;A database update that unlocks access&lt;/li&gt;
&lt;li&gt;A protected page that checks entitlement before rendering&lt;/li&gt;
&lt;li&gt;A manual test checklist before launch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most bugs happen around steps 3-5.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Webhook Checklist
&lt;/h2&gt;

&lt;p&gt;Before I consider a payment flow production-ready, I check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the webhook signature verified?&lt;/li&gt;
&lt;li&gt;Is the event idempotent?&lt;/li&gt;
&lt;li&gt;Can duplicate webhook delivery cause duplicate access or duplicate records?&lt;/li&gt;
&lt;li&gt;Is the user mapped through a stable id, not just an email string?&lt;/li&gt;
&lt;li&gt;What happens when payment succeeds but the browser tab closes?&lt;/li&gt;
&lt;li&gt;What happens when payment fails?&lt;/li&gt;
&lt;li&gt;Can the user regain access from a different device?&lt;/li&gt;
&lt;li&gt;Are secrets only stored in environment variables?&lt;/li&gt;
&lt;li&gt;Is there a simple admin/debug view for recent payment events?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why I Like A Fixed Scope
&lt;/h2&gt;

&lt;p&gt;For the first revenue version, fixed scope beats open-ended development.&lt;/p&gt;

&lt;p&gt;A good first scope is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one product or one subscription tier&lt;/li&gt;
&lt;li&gt;one checkout path&lt;/li&gt;
&lt;li&gt;one protected dashboard or download area&lt;/li&gt;
&lt;li&gt;one webhook integration&lt;/li&gt;
&lt;li&gt;one deployment&lt;/li&gt;
&lt;li&gt;one test checklist&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;complex multi-plan billing&lt;/li&gt;
&lt;li&gt;custom accounting&lt;/li&gt;
&lt;li&gt;marketplace payouts&lt;/li&gt;
&lt;li&gt;advanced admin dashboards&lt;/li&gt;
&lt;li&gt;weeks of product feature work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the project shippable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Founder Mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Building product features before payment works&lt;/li&gt;
&lt;li&gt;Treating checkout success page as proof of payment&lt;/li&gt;
&lt;li&gt;Not testing webhook retries&lt;/li&gt;
&lt;li&gt;Using email as the only source of identity&lt;/li&gt;
&lt;li&gt;Forgetting cancellation/downgrade behavior&lt;/li&gt;
&lt;li&gt;Not writing down the manual test path&lt;/li&gt;
&lt;li&gt;Waiting too long to charge real money&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lightweight Audit Offer
&lt;/h2&gt;

&lt;p&gt;If you are building a Next.js SaaS and payments are still unclear, I offer a small payment integration audit.&lt;/p&gt;

&lt;p&gt;You send:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your stack&lt;/li&gt;
&lt;li&gt;current payment provider or desired provider&lt;/li&gt;
&lt;li&gt;pricing model&lt;/li&gt;
&lt;li&gt;repo/screenshots if available&lt;/li&gt;
&lt;li&gt;what should unlock after payment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I return:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact integration path&lt;/li&gt;
&lt;li&gt;missing pieces&lt;/li&gt;
&lt;li&gt;risk points&lt;/li&gt;
&lt;li&gt;suggested fixed-scope implementation&lt;/li&gt;
&lt;li&gt;whether Polar/Stripe/LemonSqueezy makes sense for your case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Price: $49 / TRY 1,500.&lt;/p&gt;

&lt;p&gt;If you want implementation after that, the audit fee can be credited toward the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  CTA
&lt;/h2&gt;

&lt;p&gt;If this is relevant, send me your stack and pricing model. I will tell you whether your payment flow needs a quick audit or a full implementation.&lt;/p&gt;




&lt;p&gt;I also put the audit route and direct checkout products here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Audit / review route: &lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;20 Next.js templates bundle: &lt;a href="https://cengokurtoglu.gumroad.com/l/vuhstz" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/vuhstz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI Startup template: &lt;a href="https://cengokurtoglu.gumroad.com/l/mmpgds" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/mmpgds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modern SaaS template: &lt;a href="https://cengokurtoglu.gumroad.com/l/butotw" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/butotw&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>saas</category>
      <category>payments</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Next.js 15 App Router SEO done right: generateMetadata, dynamic sitemaps, and the one-file strategy</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Tue, 30 Jun 2026 19:07:53 +0000</pubDate>
      <link>https://dev.to/cekuu35/nextjs-15-app-router-seo-done-right-generatemetadata-dynamic-sitemaps-and-the-one-file-strategy-fd4</link>
      <guid>https://dev.to/cekuu35/nextjs-15-app-router-seo-done-right-generatemetadata-dynamic-sitemaps-and-the-one-file-strategy-fd4</guid>
      <description>&lt;p&gt;SEO in Next.js App Router is genuinely good once you understand the three pieces that work together. The mistake I see in most templates and starter kits is treating them as three separate problems. They aren't — they're one system.&lt;/p&gt;

&lt;p&gt;This is a walkthrough of the exact pattern I settled on after applying it across 20 production Next.js 15 templates covering niches from SaaS and real estate to e-commerce and hospitality. All code below is from those templates, not contrived examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three pieces
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A single typed content config&lt;/strong&gt; — all text, URLs, and brand values in one place&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;generateMetadata&lt;/code&gt;&lt;/strong&gt; — static for most pages, dynamic for data-driven routes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sitemap.ts&lt;/code&gt; and &lt;code&gt;robots.ts&lt;/code&gt;&lt;/strong&gt; — auto-generated from the same config&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Get these three to read from the same source and SEO maintenance essentially disappears.&lt;/p&gt;




&lt;h2&gt;
  
  
  Piece 1: the content config
&lt;/h2&gt;

&lt;p&gt;Every template has a &lt;code&gt;src/lib/content.ts&lt;/code&gt; (or &lt;code&gt;data.ts&lt;/code&gt;) that exports typed constants for everything the site needs to display — and everything the meta layer needs to index it.&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/lib/content.ts&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;SITE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nexus&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tagline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The platform that scales with you.&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;Analytics, automation, and collaboration in one platform. Ship faster, decide smarter, grow without limits.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_SITE_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://nexus.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello@nexus.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@nexushq&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="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;as const&lt;/code&gt; is load-bearing: TypeScript narrows the types to literals, so if you reference &lt;code&gt;SITE.name&lt;/code&gt; in a metadata title template, you get a type error the moment you accidentally delete the field. The entire SEO layer catches regressions at build time, not after a deploy.&lt;/p&gt;

&lt;p&gt;The key discipline: &lt;strong&gt;if a value appears in a heading, it should also appear in the metadata. Both read from the same object.&lt;/strong&gt; No copy-paste drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  Piece 2a: static metadata (the layout level)
&lt;/h2&gt;

&lt;p&gt;For the root layout, export &lt;code&gt;metadata&lt;/code&gt; directly. The &lt;code&gt;title.template&lt;/code&gt; field is the one most people skip — it lets every child page get a consistent &lt;code&gt;"Page name · Nexus"&lt;/code&gt; format for free, without each page re-specifying the brand.&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/app/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Viewport&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;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SITE&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;@/lib/content&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;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;metadataBase&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SITE&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="c1"&gt;// resolves relative OG image paths&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;default&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;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagline&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="c1"&gt;// home page&lt;/span&gt;
    &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`%s · &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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="c1"&gt;// every other page&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="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&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;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;openGraph&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — Modern SaaS Platform`&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="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SITE&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="na"&gt;siteName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en_US&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;twitter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary_large_image&lt;/span&gt;&lt;span class="dl"&gt;"&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — Modern SaaS Platform`&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="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SITE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;twitter&lt;/span&gt;&lt;span class="p"&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="na"&gt;googleBot&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-image-preview&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;large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Viewport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;themeColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0a0a0f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dark&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;metadataBase&lt;/code&gt;&lt;/strong&gt; is required for Open Graph images to resolve. Skip it and Next.js will warn on every build that your OG image URLs are relative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Viewport&lt;/code&gt; is now separate&lt;/strong&gt; from &lt;code&gt;Metadata&lt;/code&gt; in Next.js 15. Putting &lt;code&gt;themeColor&lt;/code&gt; inside &lt;code&gt;metadata&lt;/code&gt; still works but triggers a deprecation warning. Keep them in separate exports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;robots&lt;/code&gt; in metadata generates the &lt;code&gt;&amp;lt;meta name="robots"&amp;gt;&lt;/code&gt; tag&lt;/strong&gt;, not &lt;code&gt;robots.txt&lt;/code&gt;. You still want &lt;code&gt;robots.ts&lt;/code&gt; for the actual file (covered below).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Piece 2b: dynamic metadata for data-driven routes
&lt;/h2&gt;

&lt;p&gt;Static &lt;code&gt;export const metadata&lt;/code&gt; doesn't work when the page content changes per URL — a real estate listing, a blog post, a product page. For those, export &lt;code&gt;generateMetadata&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;Here's the real-estate listing detail page from one of the templates:&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/app/ilan/[slug]/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Metadata&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;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;listings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formatPrice&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;@/data/listings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Pre-render at build time (optional but recommended for static content)&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;generateStaticParams&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;listings&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;l&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="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Per-page SEO from the data&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;generateMetadata&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;params&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="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;slug&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="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// &amp;lt;-- Promise in Next.js 15&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Metadata&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;slug&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;params&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;listing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;listings&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;l&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;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;slug&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="nx"&gt;listing&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;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;Listing not found&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="na"&gt;title&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;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&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="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listing&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;155&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="s2"&gt;`/ilan/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="na"&gt;openGraph&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&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="nf"&gt;formatPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listing&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="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&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="mi"&gt;155&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;website&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;&lt;code&gt;params&lt;/code&gt; is now a &lt;code&gt;Promise&lt;/code&gt;&lt;/strong&gt; in Next.js 15 — this catches people who copy patterns from Next.js 13/14. If you destructure &lt;code&gt;params&lt;/code&gt; synchronously (the old way), you get a runtime error in production even though it might work locally.&lt;/p&gt;

&lt;p&gt;The description slice at 155 characters is intentional: Google truncates around 160. Slice at the data level so the metadata object always has a valid string, not a value that might be 3,000 characters from a rich text field.&lt;/p&gt;




&lt;h2&gt;
  
  
  Piece 3: &lt;code&gt;sitemap.ts&lt;/code&gt; and &lt;code&gt;robots.ts&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Both are just TypeScript files that export functions. Next.js calls them at build time and serves the results as &lt;code&gt;/sitemap.xml&lt;/code&gt; and &lt;code&gt;/robots.txt&lt;/code&gt;. No plugin needed.&lt;/p&gt;

&lt;p&gt;The key: &lt;strong&gt;the sitemap reads from the same data array as the pages&lt;/strong&gt;. Add a listing, add a blog post — the sitemap updates on the next build without any manual maintenance.&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/app/sitemap.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&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;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;listings&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;@/data/listings&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;siteUrl&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://estora.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;// same as SITE.url; centralize if you want&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&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;staticRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&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="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                  &lt;span class="na"&gt;lastModified&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="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weekly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&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;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/listings`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="na"&gt;lastModified&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="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&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;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/about`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="na"&gt;lastModified&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="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monthly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;url&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;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/contact`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="na"&gt;lastModified&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="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monthly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&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;listingRoutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;listings&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;l&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="na"&gt;url&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;siteUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ilan/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&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="na"&gt;lastModified&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;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listedDate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weekly&lt;/span&gt;&lt;span class="dl"&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="c1"&gt;// &amp;lt;-- const assertion required to satisfy the union type&lt;/span&gt;
    &lt;span class="na"&gt;priority&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="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;staticRoutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;listingRoutes&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/robots.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&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;next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;robots&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Robots&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;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userAgent&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;allow&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;sitemap&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://estora.com/sitemap.xml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;"weekly" as const&lt;/code&gt; on the dynamic routes is a small gotcha: &lt;code&gt;changeFrequency&lt;/code&gt; is a string union type, and TypeScript will complain if the inferred type is just &lt;code&gt;string&lt;/code&gt;. Either use &lt;code&gt;as const&lt;/code&gt; on the value or annotate the array with &lt;code&gt;MetadataRoute.Sitemap&lt;/code&gt; explicitly (which forces the type down).&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it together: what the build output looks like
&lt;/h2&gt;

&lt;p&gt;Run &lt;code&gt;next build&lt;/code&gt; and you get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Route (app)                           Size
├ ○ /                                 5.2 kB
├ ○ /about                            3.1 kB
├ ● /ilan/[slug]                      4.8 kB
│   ├ /ilan/modern-penthouse
│   ├ /ilan/garden-apartment
│   └ ... (12 more pages)
├ ○ /sitemap.xml
└ ○ /robots.txt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every listing page is pre-rendered with its own title, description, and canonical URL baked in. The sitemap lists all of them. No client-side rendering, no runtime SEO gaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one mistake that costs rankings
&lt;/h2&gt;

&lt;p&gt;Canonical URLs. Most templates either skip them or put a hardcoded string in every page file. Both are wrong.&lt;/p&gt;

&lt;p&gt;The right approach: canonical at the layout level defaults to the root path, then each dynamic page overrides it with &lt;code&gt;alternates: { canonical: \&lt;/code&gt;/ilan/\${slug}&lt;code&gt;}&lt;/code&gt;. Pages that don't override inherit the root canonical, which is fine for static pages. Pages that do override it tell Google exactly which URL is the source of truth.&lt;/p&gt;

&lt;p&gt;Without the override on dynamic routes, you can end up with dozens of listing pages all canonicalizing to &lt;code&gt;/&lt;/code&gt;, which signals duplicate content to crawlers even though the pages are distinct.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this pattern scales
&lt;/h2&gt;

&lt;p&gt;When you apply this to 20 templates across completely different domains, the pattern holds because it's mechanical: one config object, one metadata export per route level, one sitemap function. The content changes; the SEO structure doesn't.&lt;/p&gt;

&lt;p&gt;The templates I mentioned earlier cover SaaS, real estate, restaurants, e-commerce, gyms, dental clinics, hotels, law firms, and more. Every one uses this exact three-piece pattern. Live demos are at &lt;strong&gt;&lt;a href="https://cenkkurtoglu.com/templates" rel="noopener noreferrer"&gt;cenkkurtoglu.com/templates&lt;/a&gt;&lt;/strong&gt; if you want to inspect the rendered markup — check the source on any listing page and you'll see the pattern in action.&lt;/p&gt;

&lt;p&gt;The code is also the starting point if you'd rather skip the boilerplate and build the domain-specific logic directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Key detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Brand/URL constants&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/lib/content.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;as const&lt;/code&gt;, single source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root metadata&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/app/layout.tsx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;metadataBase&lt;/code&gt;, &lt;code&gt;title.template&lt;/code&gt;, separate &lt;code&gt;viewport&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic page metadata&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;generateMetadata&lt;/code&gt; in &lt;code&gt;[slug]/page.tsx&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;params&lt;/code&gt; is a &lt;code&gt;Promise&lt;/code&gt; in Next.js 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static pre-rendering&lt;/td&gt;
&lt;td&gt;&lt;code&gt;generateStaticParams&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pair with &lt;code&gt;generateMetadata&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sitemap&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/app/sitemap.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads same data array as pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Robots&lt;/td&gt;
&lt;td&gt;&lt;code&gt;src/app/robots.ts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Points to sitemap URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canonicals&lt;/td&gt;
&lt;td&gt;&lt;code&gt;alternates.canonical&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Override per dynamic route&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>nextjs</category>
      <category>seo</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>23 Production-Ready Next.js 15 + Tailwind Templates (Live Demos You Can Click)</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Sun, 28 Jun 2026 08:39:33 +0000</pubDate>
      <link>https://dev.to/cekuu35/23-production-ready-nextjs-15-tailwind-templates-live-demos-you-can-click-5pa</link>
      <guid>https://dev.to/cekuu35/23-production-ready-nextjs-15-tailwind-templates-live-demos-you-can-click-5pa</guid>
      <description>&lt;p&gt;I spent the last few weeks building a set of &lt;strong&gt;23 Next.js 15 + Tailwind CSS templates&lt;/strong&gt; — one per common business use-case — and deploying every single one to Vercel so you can click and feel them, not just look at a screenshot.&lt;/p&gt;

&lt;p&gt;Here's the full gallery, grouped by what you'd actually use them for. All live, all responsive, all with Framer Motion animations.&lt;/p&gt;

&lt;h2&gt;
  
  
  SaaS &amp;amp; tech
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://01-modern-saas.vercel.app" rel="noopener noreferrer"&gt;Modern SaaS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://02-ai-startup.vercel.app" rel="noopener noreferrer"&gt;AI Startup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://15-mobile-app.vercel.app" rel="noopener noreferrer"&gt;Mobile App Landing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://14-web3-crypto.vercel.app" rel="noopener noreferrer"&gt;Web3 / Crypto&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Services &amp;amp; local business
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://03-digital-agency.vercel.app" rel="noopener noreferrer"&gt;Digital Agency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://09-dental-clinic.vercel.app" rel="noopener noreferrer"&gt;Dental Clinic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://19-law-firm.vercel.app" rel="noopener noreferrer"&gt;Law Firm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://11-barber-salon.vercel.app" rel="noopener noreferrer"&gt;Barber &amp;amp; Salon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://06-fitness-gym.vercel.app" rel="noopener noreferrer"&gt;Fitness &amp;amp; Gym&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://18-construction.vercel.app" rel="noopener noreferrer"&gt;Construction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://05-real-estate.vercel.app" rel="noopener noreferrer"&gt;Real Estate&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Food &amp;amp; hospitality
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://04-restaurant-cafe.vercel.app" rel="noopener noreferrer"&gt;Restaurant &amp;amp; Cafe&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://10-restaurant-reservation.vercel.app" rel="noopener noreferrer"&gt;Restaurant Reservation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://17-hotel-booking.vercel.app" rel="noopener noreferrer"&gt;Hotel Booking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Commerce, content &amp;amp; personal
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://07-ecommerce-store.vercel.app" rel="noopener noreferrer"&gt;E-commerce Store&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://08-online-course.vercel.app" rel="noopener noreferrer"&gt;Online Course&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://16-newsletter-creator.vercel.app" rel="noopener noreferrer"&gt;Newsletter / Creator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://12-photography-portfolio.vercel.app" rel="noopener noreferrer"&gt;Photography Portfolio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://13-event-conference.vercel.app" rel="noopener noreferrer"&gt;Event &amp;amp; Conference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://20-personal-cv.vercel.app" rel="noopener noreferrer"&gt;Personal CV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three quality tiers
&lt;/h2&gt;

&lt;p&gt;To keep the trade-off between speed and polish explicit, three of them are tier references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://22-basic-tier.vercel.app" rel="noopener noreferrer"&gt;Basic — Clean Studio&lt;/a&gt; (lightweight, CSS-only)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://23-standard-tier.vercel.app" rel="noopener noreferrer"&gt;Standard — Meridian&lt;/a&gt; (Framer Motion mid-tier)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://21-ultra-premium.vercel.app" rel="noopener noreferrer"&gt;Premium — Nexus Studio&lt;/a&gt; (cinematic 3D flagship)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One thing that made these maintainable
&lt;/h2&gt;

&lt;p&gt;Every template reads its brand, colors, and copy from a &lt;strong&gt;single config file&lt;/strong&gt;. Want to rebrand the dental template for a different clinic? You touch one file, not 14 components. That single-source-of-truth pattern is the difference between a template you can ship in an afternoon and one you fight for a week.&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/lib/config.ts — change once, propagates everywhere&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;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Acme Clinic&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#0ea5e9&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// ...nav, hero, services, CTA&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Grab them
&lt;/h2&gt;

&lt;p&gt;The full source is browsable on &lt;a href="https://github.com/cekuu35/website-templates" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and if you want the packaged source to build on, the tiers are on &lt;a href="https://cengokurtoglu.gumroad.com" rel="noopener noreferrer"&gt;Gumroad&lt;/a&gt; (use &lt;code&gt;LAUNCH20&lt;/code&gt; for 20% off while it's new).&lt;/p&gt;

&lt;p&gt;If you'd rather have one tailored to your brand and delivered done, that's what my &lt;a href="https://www.fiverr.com/cengo3509" rel="noopener noreferrer"&gt;Fiverr&lt;/a&gt; gig is for.&lt;/p&gt;

&lt;p&gt;Happy to answer any Next.js / Tailwind questions in the comments 👇&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>tailwindcss</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>New Technical Article</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Tue, 23 Jun 2026 10:19:06 +0000</pubDate>
      <link>https://dev.to/cekuu35/new-technical-article-355n</link>
      <guid>https://dev.to/cekuu35/new-technical-article-355n</guid>
      <description>&lt;p&gt;Makale hazÄ±r. Dosya: &lt;code&gt;C:\Users\cnkku\income-infrastructure\ecommerce-cro-ultimate-guide.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ne yazdÄ±m â€” "The Ultimate Guide to E-commerce Conversion Rate Optimization"&lt;/strong&gt; (geliÅŸtirici odaklÄ±, Ä°ngilizce â€” Dev.to/Medium Ä°ngilizce platformlar olduÄŸu iÃ§in):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to front matter&lt;/strong&gt; (title/tags/canonical_url) hazÄ±r â€” yapÄ±ÅŸtÄ±r-yayÄ±nla&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9 bÃ¶lÃ¼m + iÃ§indekiler&lt;/strong&gt; (anchor linkli), ~2800 kelime, uzun-form&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kod Ã¶rnekleri&lt;/strong&gt; (gerÃ§ek, Ã§alÄ±ÅŸÄ±r seviyede):

&lt;ul&gt;
&lt;li&gt;Funnel event tracker (&lt;code&gt;sendBeacon&lt;/code&gt; + &lt;code&gt;keepalive&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web-vitals&lt;/code&gt; ile saha Ã¶lÃ§Ã¼mÃ¼ (LCP/INP/CLS)&lt;/li&gt;
&lt;li&gt;LCP fix (Next.js &lt;code&gt;Image priority&lt;/code&gt;, preload, AVIF/WebP)&lt;/li&gt;
&lt;li&gt;CLS fix (&lt;code&gt;aspect-ratio&lt;/code&gt;, font-display)&lt;/li&gt;
&lt;li&gt;INP fix (main-thread yielding, optimistic UI)&lt;/li&gt;
&lt;li&gt;Guest checkout, inline validation, cart recovery&lt;/li&gt;
&lt;li&gt;Typed analytics layer + consent gating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B test matematiÄŸi&lt;/strong&gt;: deterministik hash bucketing, sample-size hesabÄ±, z-test + normalCdf â€” bu kÄ±sÄ±m Ã§oÄŸu CRO yazÄ±sÄ±nda olmaz, mÃ¼hendise hitap eder&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO uyumu&lt;/strong&gt;: anahtar kelime baÅŸlÄ±klarda/H2'lerde, iÃ§indekiler, karÅŸÄ±laÅŸtÄ±rma tablolarÄ±, JSON-LD structured data Ã¶rneÄŸi, audit checklist&lt;/li&gt;
&lt;li&gt;Sonda CTA (Dev.to engagement iÃ§in)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;YayÄ±n notu (senin elin):&lt;/strong&gt; Dev.to'ya &lt;code&gt;published: false&lt;/code&gt; ile gidiyor (Ã¶nce taslak gÃ¶r, sonra yayÄ±nla). Medium'da ise canonical_url'i Ã¶nce Dev.to'da yayÄ±nlayÄ±p o linki koyman SEO aÃ§Ä±sÄ±ndan doÄŸru olur (duplicate content cezasÄ± yememek iÃ§in tek kanonik kaynak).&lt;/p&gt;

&lt;p&gt;Ä°stersen TÃ¼rkÃ§e bir versiyonunu da Ã§Ä±karabilirim ya da kapak gÃ¶rseli iÃ§in Higgsfield ile bir hero gÃ¶rseli Ã¼rettirebilirim.&lt;/p&gt;




&lt;p&gt;🚀 &lt;strong&gt;Build faster:&lt;/strong&gt; I build and sell modern, production-ready Next.js templates. Check out my templates at &lt;a href="https://cenkkurtoglu.com/?ref=devto" rel="noopener noreferrer"&gt;cenkkurtoglu.com&lt;/a&gt; to save weeks of development time!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>saas</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Top 5 Modern Next.js SaaS Templates to Launch Your Startup in 2026</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Tue, 23 Jun 2026 10:18:49 +0000</pubDate>
      <link>https://dev.to/cekuu35/top-5-modern-nextjs-saas-templates-to-launch-your-startup-in-2026-iob</link>
      <guid>https://dev.to/cekuu35/top-5-modern-nextjs-saas-templates-to-launch-your-startup-in-2026-iob</guid>
      <description>&lt;p&gt;If you're building a SaaS startup in 2026, starting from scratch is the slowest route to market. The ecosystem has matured, and the boilerplate code required for authentication, payments (Stripe/Polar), database connections, and landing page UI components can take weeks to configure properly.&lt;/p&gt;

&lt;p&gt;Instead of reinventing the wheel, successful indie hackers and founders are using production-ready Next.js SaaS templates. In this article, we'll cover why you need a starter kit and what to look for when choosing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Next.js for Your SaaS?
&lt;/h2&gt;

&lt;p&gt;Next.js remains the undisputed king of React frameworks. With features like Server Components, Turbopack, and edge rendering, it provides the perfect balance of SEO performance and dynamic application capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes a Great SaaS Template?
&lt;/h2&gt;

&lt;p&gt;A solid starter kit should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Pre-configured with Supabase, Firebase, or NextAuth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payments:&lt;/strong&gt; Stripe or Polar.sh integration for subscriptions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Prisma or Drizzle ORM configured with PostgreSQL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI Components:&lt;/strong&gt; Tailwind CSS and Radix/shadcn UI for accessible, beautiful components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO &amp;amp; Blog:&lt;/strong&gt; Built-in MDX blog for inbound marketing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stop Building Boilerplates, Start Shipping
&lt;/h2&gt;

&lt;p&gt;The hardest part of any startup is getting the first 100 paying customers, not writing the authentication logic. By leveraging a high-quality Next.js template, you can skip the boring setup phase and immediately focus on building your core features and marketing your product.&lt;/p&gt;

&lt;p&gt;Happy coding and keep shipping!&lt;/p&gt;




&lt;h2&gt;
  
  
  ?? &lt;strong&gt;Build faster:&lt;/strong&gt; I build and sell modern, production-ready Next.js templates. Check out my templates at &lt;a href="https://cenkkurtoglu.com/?ref=devto" rel="noopener noreferrer"&gt;cenkkurtoglu.com&lt;/a&gt; to save weeks of development time!
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I now keep the direct checkout links here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;20 Next.js templates bundle: &lt;a href="https://cengokurtoglu.gumroad.com/l/vuhstz" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/vuhstz&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AI Startup landing page: &lt;a href="https://cengokurtoglu.gumroad.com/l/mmpgds" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/mmpgds&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modern SaaS landing page: &lt;a href="https://cengokurtoglu.gumroad.com/l/butotw" rel="noopener noreferrer"&gt;https://cengokurtoglu.gumroad.com/l/butotw&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Audit / review route: &lt;a href="https://productized-webdev.vercel.app/audit" rel="noopener noreferrer"&gt;https://productized-webdev.vercel.app/audit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>saas</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Untitled</title>
      <dc:creator>Cenk KURTOĞLU</dc:creator>
      <pubDate>Tue, 23 Jun 2026 09:01:02 +0000</pubDate>
      <link>https://dev.to/cekuu35/untitled-2e5h</link>
      <guid>https://dev.to/cekuu35/untitled-2e5h</guid>
      <description>&lt;p&gt;Makale hazÄ±r. Dosya: &lt;code&gt;C:\Users\cnkku\income-infrastructure\ecommerce-cro-ultimate-guide.md&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ne yazdÄ±m â€” "The Ultimate Guide to E-commerce Conversion Rate Optimization"&lt;/strong&gt; (geliÅŸtirici odaklÄ±, Ä°ngilizce â€” Dev.to/Medium Ä°ngilizce platformlar olduÄŸu iÃ§in):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev.to front matter&lt;/strong&gt; (title/tags/canonical_url) hazÄ±r â€” yapÄ±ÅŸtÄ±r-yayÄ±nla&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;9 bÃ¶lÃ¼m + iÃ§indekiler&lt;/strong&gt; (anchor linkli), ~2800 kelime, uzun-form&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kod Ã¶rnekleri&lt;/strong&gt; (gerÃ§ek, Ã§alÄ±ÅŸÄ±r seviyede):

&lt;ul&gt;
&lt;li&gt;Funnel event tracker (&lt;code&gt;sendBeacon&lt;/code&gt; + &lt;code&gt;keepalive&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;web-vitals&lt;/code&gt; ile saha Ã¶lÃ§Ã¼mÃ¼ (LCP/INP/CLS)&lt;/li&gt;
&lt;li&gt;LCP fix (Next.js &lt;code&gt;Image priority&lt;/code&gt;, preload, AVIF/WebP)&lt;/li&gt;
&lt;li&gt;CLS fix (&lt;code&gt;aspect-ratio&lt;/code&gt;, font-display)&lt;/li&gt;
&lt;li&gt;INP fix (main-thread yielding, optimistic UI)&lt;/li&gt;
&lt;li&gt;Guest checkout, inline validation, cart recovery&lt;/li&gt;
&lt;li&gt;Typed analytics layer + consent gating&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A/B test matematiÄŸi&lt;/strong&gt;: deterministik hash bucketing, sample-size hesabÄ±, z-test + normalCdf â€” bu kÄ±sÄ±m Ã§oÄŸu CRO yazÄ±sÄ±nda olmaz, mÃ¼hendise hitap eder&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO uyumu&lt;/strong&gt;: anahtar kelime baÅŸlÄ±klarda/H2'lerde, iÃ§indekiler, karÅŸÄ±laÅŸtÄ±rma tablolarÄ±, JSON-LD structured data Ã¶rneÄŸi, audit checklist&lt;/li&gt;
&lt;li&gt;Sonda CTA (Dev.to engagement iÃ§in)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;YayÄ±n notu (senin elin):&lt;/strong&gt; Dev.to'ya &lt;code&gt;published: false&lt;/code&gt; ile gidiyor (Ã¶nce taslak gÃ¶r, sonra yayÄ±nla). Medium'da ise canonical_url'i Ã¶nce Dev.to'da yayÄ±nlayÄ±p o linki koyman SEO aÃ§Ä±sÄ±ndan doÄŸru olur (duplicate content cezasÄ± yememek iÃ§in tek kanonik kaynak).&lt;/p&gt;

&lt;p&gt;Ä°stersen TÃ¼rkÃ§e bir versiyonunu da Ã§Ä±karabilirim ya da kapak gÃ¶rseli iÃ§in Higgsfield ile bir hero gÃ¶rseli Ã¼rettirebilirim.&lt;/p&gt;

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