<?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: Aniq UI</title>
    <description>The latest articles on DEV Community by Aniq UI (@aniqui).</description>
    <link>https://dev.to/aniqui</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%2F3171599%2F1f7587c3-d7a2-4334-bae6-05b0ae1536cc.png</url>
      <title>DEV Community: Aniq UI</title>
      <link>https://dev.to/aniqui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aniqui"/>
    <language>en</language>
    <item>
      <title>How to Build an LMS with Live Classes in Next.js</title>
      <dc:creator>Aniq UI</dc:creator>
      <pubDate>Sun, 18 Oct 2026 20:44:06 +0000</pubDate>
      <link>https://dev.to/aniqui/how-to-build-an-lms-with-live-classes-in-nextjs-1j8f</link>
      <guid>https://dev.to/aniqui/how-to-build-an-lms-with-live-classes-in-nextjs-1j8f</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.aniq-ui.com/en/blog/build-lms-live-classes-nextjs" rel="noopener noreferrer"&gt;Aniq UI blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By the end of this guide you will know how to build an online course platform with recorded lessons, live classes, quizzes, certificates and paid checkout, using Next.js, NestJS and the Learnio LMS template from Aniq UI. You will not have to build a student site, an admin panel and an API separately.&lt;/p&gt;

&lt;p&gt;We follow one example school from its first plan to launch. Each step names the screen or setting involved, and the commands come straight from the template's documentation. The same path works for a solo course creator, a training company, or an agency building an LMS for a client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;About this example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ledgerline Academy is an illustrative business we use to show the steps. It is not a client story, and the numbers in it are examples, not results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The brief
&lt;/h2&gt;

&lt;p&gt;Ledgerline Academy is a small online school that teaches bookkeeping to people changing careers. Two instructors teach. The school wants to start with a few courses and grow. Here is what it needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A free introductory course that anyone can take, so visitors can try the school before paying.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paid courses with recorded lessons, personal notes and downloadable worksheets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A live class every week for each cohort, held inside the platform instead of a separate meeting link.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quizzes with a pass mark, and assignments that an instructor marks with feedback.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A certificate at the end of a course, with a page where an employer can check it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Two instructors who each manage only their own courses and students.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An English and an Arabic version of the whole school.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the template covers the brief
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;What you use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free and paid courses&lt;/td&gt;
&lt;td&gt;Student site and API&lt;/td&gt;
&lt;td&gt;Course pages and checkout; a price of 0 skips payment entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Course content&lt;/td&gt;
&lt;td&gt;Admin dashboard&lt;/td&gt;
&lt;td&gt;The curriculum builder with sections, lessons and draft or published states&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Notes and worksheets&lt;/td&gt;
&lt;td&gt;Student site&lt;/td&gt;
&lt;td&gt;The course player, with notes and downloadable resources per lesson&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weekly live class&lt;/td&gt;
&lt;td&gt;Admin dashboard, student site, your meeting server&lt;/td&gt;
&lt;td&gt;Live classes, the course calendar, and a Jitsi Meet server you run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quizzes and assignments&lt;/td&gt;
&lt;td&gt;Admin dashboard and student site&lt;/td&gt;
&lt;td&gt;The quiz editor, assignment grading, graded on the server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificates&lt;/td&gt;
&lt;td&gt;Student site&lt;/td&gt;
&lt;td&gt;Certificates page and the public verify page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instructors see their own courses&lt;/td&gt;
&lt;td&gt;Admin dashboard and API&lt;/td&gt;
&lt;td&gt;The Instructor role with an instructor profile linked to the account&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taking payment&lt;/td&gt;
&lt;td&gt;API and student site&lt;/td&gt;
&lt;td&gt;Stripe Checkout and a signed webhook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English and Arabic&lt;/td&gt;
&lt;td&gt;All three apps&lt;/td&gt;
&lt;td&gt;/en and /ar routes, message files, courses written in both languages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Before you start: decisions to make
&lt;/h2&gt;

&lt;p&gt;Course platforms have a few decisions that are hard to undo once students and payments exist. Make them on paper first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Free, paid, or both?
&lt;/h3&gt;

&lt;p&gt;A course with a price of 0 enrolls the student immediately and never reaches a payment processor. A paid course goes through checkout and enrolls the student only after the processor confirms payment. Ledgerline uses both: a free intro to build trust, and paid courses behind it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recorded, live, or both?
&lt;/h3&gt;

&lt;p&gt;The catalog can be filtered by live or recorded. Recorded courses need only lessons and a media bucket. Live courses also need a meeting server that you host. Decide whether live classes are part of launch, because the server is a separate piece of infrastructure with its own cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who teaches, and what may they touch?
&lt;/h3&gt;

&lt;p&gt;If one person does everything, you can use one super admin account. If you have several teachers, plan an instructor profile and a linked account for each.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which payment processors?
&lt;/h3&gt;

&lt;p&gt;The template supports Stripe cards and PayPal. The checkout always lists both, so plan to configure both before taking real money. If you want only one, plan a code change to the checkout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which languages?
&lt;/h3&gt;

&lt;p&gt;English and Arabic ship ready. Course content holds exactly two languages: an English and an Arabic value for each title, description and lesson. A third interface language is possible, but a third content language means changes in the API and the admin editors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do video and files live?
&lt;/h3&gt;

&lt;p&gt;Course art, lesson video and handouts go to a Cloudflare R2 bucket or another S3-compatible store. Decide who owns the bucket and what you will do about backups.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do you tell people when they buy?
&lt;/h3&gt;

&lt;p&gt;Receipts and enrollment confirmations go through Resend, which needs a verified sending domain. Plan the domain and the sender address before you test email.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Choose the package
&lt;/h3&gt;

&lt;p&gt;Learnio is three apps: a student website, an admin and instructor dashboard, and a NestJS API behind both. You can buy them together as the Full Stack package, or one at a time. The student site alone runs on bundled sample data. The admin dashboard alone runs on an in-browser mock, and most of its screens stay empty until an API is connected.&lt;/p&gt;

&lt;p&gt;Ledgerline needs students, instructors and a database that all agree, so it takes the Full Stack package. If you already have an API, a single frontend can point at it. The commercial license covers unlimited projects, including work you deliver to a client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpiz0oc825157dppzza7q.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpiz0oc825157dppzza7q.webp" alt="Documentation video still of the Learnio LMS template: downloading the ZIP for your package" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the My templates page: pick the ZIP that matches the package you bought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; the extracted folder for the full stack is &lt;code&gt;learnio-lms&lt;/code&gt;, and it contains &lt;code&gt;admin-dashboard&lt;/code&gt;, &lt;code&gt;back-end&lt;/code&gt;, &lt;code&gt;frontend&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt;. Single-app packages extract to a folder of their own, with a &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;.env.example&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Run everything with Docker
&lt;/h3&gt;

&lt;p&gt;Download the ZIP from My templates, unzip it, and open a terminal in the &lt;code&gt;learnio-lms&lt;/code&gt; folder. With Docker running, start the three apps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first start builds three images and seeds the database with demo data, so it takes a few minutes. Afterward the student site is at &lt;code&gt;localhost:3030&lt;/code&gt;, the admin at &lt;code&gt;localhost:3031&lt;/code&gt;, and the API at &lt;code&gt;localhost:8000&lt;/code&gt;. If a port is busy, add &lt;code&gt;SITE_PORT&lt;/code&gt;, &lt;code&gt;ADMIN_PORT&lt;/code&gt; or &lt;code&gt;API_PORT&lt;/code&gt; to a &lt;code&gt;.env&lt;/code&gt; file next to &lt;code&gt;docker-compose.yml&lt;/code&gt; and run the command again.&lt;/p&gt;

&lt;p&gt;Three demo accounts let you see each side: a student (&lt;code&gt;demo@learnio.com&lt;/code&gt;, password &lt;code&gt;Demo@123&lt;/code&gt;), a super admin (&lt;code&gt;admin@learnio.com&lt;/code&gt;, password &lt;code&gt;Admin@123&lt;/code&gt;) and an instructor (&lt;code&gt;emily-davis@learnio.com&lt;/code&gt;, password &lt;code&gt;Teach@123&lt;/code&gt;). Sign in as each one and click around before you change anything. Without Docker, you need Node.js 22 or 24, &lt;code&gt;corepack enable&lt;/code&gt;, and one terminal per app, API first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwlyqbqr48qq23ud2mz9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwlyqbqr48qq23ud2mz9.webp" alt="Documentation video still of the Learnio LMS template: unzipping the download and opening the folder" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This still shows the terminal opened inside the extracted folder, the place every command here assumes you are standing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu56ld8yqwyc5zpplwwut.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu56ld8yqwyc5zpplwwut.webp" alt="Documentation video still of the Learnio LMS template: starting all three apps with Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Watch the build output: three images are built, and the two frontends hold back until the API reports healthy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwj7orwo0hnswzc9e9q98.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwj7orwo0hnswzc9e9q98.webp" alt="Documentation video still of the Learnio LMS template: starting the admin dashboard without Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The admin runs in its own terminal on port 3031 once the API is up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiopfxxsfdfx2dr11owlu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiopfxxsfdfx2dr11owlu.jpg" alt="Admin dashboard overview in the Learnio LMS template, light theme" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the administrator overview you should see after signing in as the super admin: cards and charts for the platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; open &lt;code&gt;localhost:8000/api/health&lt;/code&gt;, which should answer with a status of ok, then sign in on each app with the demo accounts. If a page carries a notice about sample data, that app could not reach the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker is not running, or a port is taken. Free port 3030, 3031 or 8000, or set &lt;code&gt;SITE_PORT&lt;/code&gt;, &lt;code&gt;ADMIN_PORT&lt;/code&gt; or &lt;code&gt;API_PORT&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The first start seems stuck. It is seeding the demo data, and the frontends start only when the API is healthy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yarn reports version 1.22. Run &lt;code&gt;corepack enable&lt;/code&gt; and install again.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A fresh run on a database you keep does not reset anything. &lt;code&gt;docker compose down -v&lt;/code&gt; deletes the volume and the next start seeds fresh demo data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Set the school's name, logo and colors
&lt;/h3&gt;

&lt;p&gt;The brand lives in files, and the student site and the admin keep separate copies, so change both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Student site: &lt;code&gt;src/config/site.ts&lt;/code&gt; holds the name, description and social handles. &lt;code&gt;src/config/brand.config.ts&lt;/code&gt; names the student dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Admin: &lt;code&gt;src/config/brand.config.ts&lt;/code&gt; holds the name, short name, company and the metadata base address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logos: replace &lt;code&gt;public/assets/brand/learnio-wordmark.png&lt;/code&gt; and &lt;code&gt;learnio-mark.png&lt;/code&gt; with your own transparent PNGs at the same names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Colors: &lt;code&gt;src/styles/theme-variables.css&lt;/code&gt; in each app, with light values under &lt;code&gt;:root&lt;/code&gt; and dark values under &lt;code&gt;.dark&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The App Settings tab in the admin does not rebrand the apps. Its &lt;code&gt;site_name&lt;/code&gt; and &lt;code&gt;support_email&lt;/code&gt; only change the emails the API sends. Search both message folders for the old name in English and Arabic, and set the site's public address in &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; change the name in both apps, rebuild, and look for the old name on the sign-in pages, in the footer and in the browser tab, in English and in Arabic. Replace the favicon and the sharing images too, since link previews use them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; only one of the two apps is rebranded, or the logo is stretched because its proportions differ from the declared size. If your artwork has another shape, update the declared width and height where the sidebar brand and the logo component set them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Build the first course
&lt;/h3&gt;

&lt;p&gt;Open Courses in the admin and create the free intro course. Add sections, then lessons inside them, and drag to reorder. A lesson can be a video, a reading, a quiz or an assignment. Attach worksheets as lesson resources, and leave the course as a draft until it is ready. Only published public courses reach the catalog.&lt;/p&gt;

&lt;p&gt;Titles, descriptions and lessons are stored once with an English and an Arabic value, and the editors give you a tab for each. Ledgerline writes both before publishing. A free course has a price of 0 and needs no payment processor at all.&lt;/p&gt;

&lt;p&gt;Uploads (video, handouts, course art) go to a Cloudflare R2 or other S3-compatible bucket once you set the five &lt;code&gt;R2_*&lt;/code&gt; variables in &lt;code&gt;back-end/.env&lt;/code&gt;. Until then the demo's bundled sample files still play, and only new uploads fail.&lt;/p&gt;

&lt;p&gt;Build it in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the course with its title, description, price and category, in both languages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add sections, then lessons inside each section, choosing a lesson type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attach worksheets and handouts to the lessons that need them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flag a lesson as a free preview if you want visitors to see a sample. Only free-preview lessons expose their content and video on the public course page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Publish the course when it is ready. Until then it stays a draft and the public API answers not found.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7rfmy2i04csowzxt4sf2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7rfmy2i04csowzxt4sf2.jpg" alt="Drag and drop curriculum builder in the Learnio LMS template" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at how sections hold their lessons, each with a handle you drag to change the order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; open the public course page in another browser. It should show the overview, syllabus, price and the enroll action. Then sign in as the demo student and open the course player to see lessons, notes and resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a course is visible in the admin but not on the site because it is still a draft or is not set to public. Another common one is a missing Arabic title, which makes the Arabic page fall back to empty fields.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Add quizzes, assignments and certificates
&lt;/h3&gt;

&lt;p&gt;In the quiz editor, add questions and answer options, then set a passing score and an attempt limit, or allow unlimited attempts for practice. The server grades every attempt, so students cannot change a score in the browser. Required quizzes count toward finishing the course.&lt;/p&gt;

&lt;p&gt;Assignments appear in the student's calendar, where they hand in a submission. The instructor opens the submissions, saves a grade, and writes feedback for that student. After every finished lesson and every passed quiz, the API re-checks whether the course is complete. When it is, the enrollment is marked complete, a certificate is issued and the student gets a notification. Anyone can then check a certificate number at &lt;code&gt;/en/verify-certificate&lt;/code&gt;, with no account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wx4qf4w04e6rtwq6yhe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wx4qf4w04e6rtwq6yhe.jpg" alt="Quiz editor in the Learnio LMS template, with questions, answers and a passing score" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprxzklsjelzru12wohiw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprxzklsjelzru12wohiw.jpg" alt="Public certificate verification page in the Learnio LMS template" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagkxprodm3jcjg8ju1rr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fagkxprodm3jcjg8ju1rr.jpg" alt="Assignment grading screen in the Learnio LMS template, light theme" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the submission waiting to be marked: the instructor sets a grade and writes feedback addressed to that student.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; take a quiz as the demo student and fail it on purpose, then pass it. Confirm the attempt limit stops you when it should. Submit an assignment from the calendar, mark it as an instructor, and look for the notification on the student side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a required quiz is left unpassed, so the certificate is not ready, and the student is told so. Also check that the passing score is a number the questions can actually reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Take payments in test mode
&lt;/h3&gt;

&lt;p&gt;Out of the box the checkout uses a built-in simulator. It contacts no processor and charges nobody, and a card number that ends in 0 is rejected, which lets you rehearse a failed payment. Run a full purchase this way first, as the demo student.&lt;/p&gt;

&lt;p&gt;To move to Stripe, create test keys in your Stripe account and put &lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt;, &lt;code&gt;STRIPE_PUBLISHABLE_KEY&lt;/code&gt; and &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; in &lt;code&gt;back-end/.env&lt;/code&gt;. Then add a webhook endpoint in Stripe that points at your API's address followed by &lt;code&gt;/api/webhooks/payments/stripe&lt;/code&gt;. A student is enrolled when the webhook confirms the payment, not when they return to the site. Without the webhook, orders stay pending.&lt;/p&gt;

&lt;p&gt;PayPal has its own keys: &lt;code&gt;PAYPAL_CLIENT_ID&lt;/code&gt;, &lt;code&gt;PAYPAL_CLIENT_SECRET&lt;/code&gt;, &lt;code&gt;PAYPAL_WEBHOOK_ID&lt;/code&gt; and &lt;code&gt;PAYPAL_ENV&lt;/code&gt;. Read the warning in the mistakes section below before you go live.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5x2fqz51gnaoq43lhmfg.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5x2fqz51gnaoq43lhmfg.jpg" alt="Stripe checkout webhook received and a Learnio order marked settled, in test mode" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frndga52tgna4nz0qof3p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frndga52tgna4nz0qof3p.jpg" alt="Checkout panel in the Learnio LMS template, light theme" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the checkout a student sees: choose card or PayPal, and the form follows the method you picked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; buy a paid course three ways. First on the simulator, to confirm the seat appears. Then decline a payment with a card number that ends in 0 and confirm no seat is granted. Then, once Stripe test keys are in, buy again and confirm the order moves from pending to paid after the webhook arrives. The order page in the admin shows each order with its course, method and status.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The order stays pending. The webhook endpoint address is wrong, or &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; does not match that endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A signed-out visitor presses pay and is sent to sign in. Paying needs a student account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The wrong processor handles a test. A method with no keys still runs the simulator.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 7: Set up live classes
&lt;/h3&gt;

&lt;p&gt;Live classes run on a Jitsi Meet server that you host. The template does not include one, and the hosting cost is yours. Without it, the calendar shows live classes as unavailable. Put the server's details in &lt;code&gt;back-end/.env&lt;/code&gt; (the example file lists the variables), and the API issues the tokens that let students and instructors into the room.&lt;/p&gt;

&lt;p&gt;An instructor schedules a session from the dashboard, and it appears in the course calendar next to assignment deadlines. Students open it from &lt;code&gt;/en/dashboard/calendar&lt;/code&gt;. The room opens shortly before the start time, with a prejoin screen first, and the instructor gets host controls. Ledgerline schedules one session per week per cohort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqrd4mfq06xxqdpz1chy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkqrd4mfq06xxqdpz1chy.jpg" alt="Course calendar in the Learnio LMS template showing live sessions and assignment deadlines" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at how live sessions and assignment deadlines share one calendar, so a student sees the whole week in a single place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; schedule a session as an instructor, then open the student calendar and confirm the class appears. Open the class page close to its start time and confirm the room becomes available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; the calendar marks classes unavailable because no meeting server is configured, or the room is opened too early. The room opens shortly before the class, not when it is scheduled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Add instructors with the right limits
&lt;/h3&gt;

&lt;p&gt;Students and staff sign in at different endpoints and receive tokens that only their own routes accept. The seed creates six staff roles: Super Admin, Admin, Manager, Editor, Viewer and Instructor. The Instructor role covers live classes, assignments, curriculum, and course editing, plus a read of students, enrollments and reviews.&lt;/p&gt;

&lt;p&gt;The key detail is the link between an admin account and an instructor profile. A linked account is also limited to its own courses: it sees only those courses and their students, and any attempt to write elsewhere gets a 403. The seeded &lt;code&gt;emily-davis@learnio.com&lt;/code&gt; account shows the setup. Ledgerline creates both instructors this way, and keeps the super admin for the owner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; sign in as the instructor and confirm the course list shows only that instructor's courses. Then try to change another instructor's course and confirm the change is refused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; an instructor sees too much because the account is not linked to an instructor profile. Or the reverse: a teacher cannot create lessons because the role lacks the curriculum permission. Edit the role on the roles screen and the change reaches signed-in staff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Connect email and, if you want, the AI assistant
&lt;/h3&gt;

&lt;p&gt;Verification links, password resets, receipts and enrollment confirmations go out through Resend, in English and Arabic. Put your &lt;code&gt;RESEND_API_KEY&lt;/code&gt; in &lt;code&gt;back-end/.env&lt;/code&gt; and verify a sending domain with Resend. Without a key nothing breaks: each message is printed in the API log instead, which is handy while testing.&lt;/p&gt;

&lt;p&gt;The admin dashboard also has an AI assistant. It can look up courses, instructors and enrollments, turn a single request into a course draft, including the Arabic title and description, schedule classes and draw registration charts. It runs on a Google, OpenAI or DeepSeek key that you pay for. With no key in &lt;code&gt;back-end/.env&lt;/code&gt;, it asks the admin for a key in the browser. Treat what it creates as a draft and review it before publishing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsoxbq6wozrx8yfrvdkzq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsoxbq6wozrx8yfrvdkzq.jpg" alt="Creating a course draft through the AI assistant in the Learnio admin dashboard, light theme" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chat shows a single request turned into a draft course, which an admin then reviews and edits like any other course.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; trigger a receipt by buying a paid course, then look in your inbox. Ask the assistant for a small draft course and open the result in the course editor to confirm both languages were filled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; email goes nowhere because the key is missing or the domain is not verified. Until a key exists, messages appear in the API log. The assistant also does not work on the admin's offline sample data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10: Go live
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Generate a secret with &lt;code&gt;node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"&lt;/code&gt; and use it as &lt;code&gt;JWT_SECRET&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In &lt;code&gt;back-end/.env&lt;/code&gt; set &lt;code&gt;NODE_ENV=production&lt;/code&gt;, &lt;code&gt;CORS_ORIGIN&lt;/code&gt; (list both frontend addresses, separated by commas) and &lt;code&gt;FRONTEND_URL&lt;/code&gt; (the student site).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In each frontend's &lt;code&gt;.env&lt;/code&gt; set &lt;code&gt;NEXT_PUBLIC_API_BASE_URL&lt;/code&gt; with &lt;code&gt;/api&lt;/code&gt; on the end, set &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; to the real domain, and rebuild. These values are compiled in.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set &lt;code&gt;NEXT_PUBLIC_WEBSOCKET_BASE_URL&lt;/code&gt; in the admin so live notifications work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you use R2, add your bucket's public host as &lt;code&gt;NEXT_PUBLIC_MEDIA_HOSTNAME&lt;/code&gt; (or &lt;code&gt;MEDIA_HOSTNAME&lt;/code&gt; with Compose) and rebuild.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change the published demo passwords, or start from empty tables.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For MySQL, create an empty database, set the driver and connection in &lt;code&gt;back-end/.env&lt;/code&gt;, and run &lt;code&gt;yarn seed&lt;/code&gt;. On a live site, &lt;code&gt;yarn db:sync&lt;/code&gt; creates the tables without writing any rows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check after going live:&lt;/strong&gt; make a real test purchase with a small amount or a discounted course, watch the order settle, confirm the enrollment email arrives, and verify the certificate page opens for someone who is not signed in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The API refuses to start with an example &lt;code&gt;JWT_SECRET&lt;/code&gt; or an empty &lt;code&gt;CORS_ORIGIN&lt;/code&gt;, and prints what to fix.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A frontend still shows the sample data notice, because its API address was missing at build time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checkout rejects a purchase from a page in a language the API does not list. Add the language to both sides before opening it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running it day to day
&lt;/h2&gt;

&lt;p&gt;After launch, Ledgerline's team works from a handful of screens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Watching the platform
&lt;/h3&gt;

&lt;p&gt;The overview shows cards and charts for the platform. Admins also receive notifications when a student is given a seat, finishes a course, leaves a review, registers, or when a course is published. An instructor hears only about their own courses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Marking and replying
&lt;/h3&gt;

&lt;p&gt;Instructors open the submissions waiting to be marked, save a grade, and leave feedback. Students and instructors keep a conversation per course, with history and image attachments, and an unread indicator.&lt;/p&gt;

&lt;h3&gt;
  
  
  Looking after orders
&lt;/h3&gt;

&lt;p&gt;Each order carries a status of pending, paid, failed or refunded, and a gateway that names the processor, the built-in simulator, or a free course. Revenue figures leave free orders out of the average order value, so a free intro does not drag it down. If you need to add a seat that was paid for elsewhere, an admin can record it by hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm4c64pd1iag9yc44kt8p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm4c64pd1iag9yc44kt8p.jpg" alt="Order detail screen in the Learnio LMS admin dashboard" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An order page like this shows the course, the payment method and the payment status in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moderating reviews
&lt;/h3&gt;

&lt;p&gt;Reviews move through published, pending and hidden. If you set &lt;code&gt;reviews_require_approval&lt;/code&gt; to true, new reviews stay pending until someone with moderation rights publishes them. Each change updates the course rating and the instructor rating.&lt;/p&gt;

&lt;h3&gt;
  
  
  People and recycle bins
&lt;/h3&gt;

&lt;p&gt;Deleting a student, instructor, course, category, review or post moves it to a recycle bin where it can be restored, so a slip is not permanent.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example launch plan by week
&lt;/h2&gt;

&lt;p&gt;This schedule is an example for Ledgerline, not a promise about how long your launch will take.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 1, understand.&lt;/strong&gt; Run the stack, sign in as each role, and settle the decisions list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 2, brand and first course.&lt;/strong&gt; Rebrand both apps and build the free intro course in both languages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 3, assessment and payments.&lt;/strong&gt; Add quizzes, assignments and certificates, then test purchases on the simulator and with Stripe test keys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 4, live classes and staff.&lt;/strong&gt; Stand up the meeting server, create the instructor accounts, and run a trial class with colleagues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 5, production.&lt;/strong&gt; Set the production environment, connect email, rebuild the frontends, and make one real purchase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 6, soft launch.&lt;/strong&gt; Open the free course first, then the paid ones.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Launch checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Names, logos, icons and colors replaced in both apps, plus sharing images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Courses published in both languages, with prices set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quizzes have reachable passing scores, and required quizzes are marked.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stripe and PayPal both configured, with webhook subscriptions in place.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A real test purchase completed and an enrollment email received.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Meeting server running and a trial class held.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instructor accounts linked to instructor profiles and tested.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;JWT_SECRET&lt;/code&gt;, &lt;code&gt;CORS_ORIGIN&lt;/code&gt; and &lt;code&gt;FRONTEND_URL&lt;/code&gt; set for production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frontend API addresses, site address and media host set, then rebuilt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Published demo passwords changed or removed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resend domain verified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Backups planned for the database and the media bucket.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Privacy and terms pages reviewed for your own business.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Template, from scratch, or a hosted course platform
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A hosted course platform
&lt;/h3&gt;

&lt;p&gt;You can have a school online quickly, with hosting, video and payments handled for you. The price is ongoing fees, limits on layout and workflow, and student data on someone else's system. It is a good fit when you want to teach and do not need custom behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building from scratch
&lt;/h3&gt;

&lt;p&gt;You decide everything. You also build accounts for two audiences, a curriculum editor, a lesson player, quizzes, grading, certificates, checkout with webhooks, email, live class tokens and two languages before you teach your first lesson.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting from a template
&lt;/h3&gt;

&lt;p&gt;You get those pieces already wired, with source code you own and can change. You host it, update it and fill the gaps that this guide names: a meeting server, your own payment accounts, a mail domain. It fits a school that wants its own brand and rules, or an agency building the platform for a client.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs to run
&lt;/h2&gt;

&lt;p&gt;Learnio packages start from $79 for a single app, and the Full Stack package Ledgerline uses is $179. It comes with a commercial license and lifetime support. The rest is what you switch on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hosting for the three apps, and a database if you move past SQLite.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stripe and PayPal charge their own processing fees. See &lt;a href="https://stripe.com/pricing" rel="noopener noreferrer"&gt;Stripe's pricing page&lt;/a&gt; and the PayPal merchant fees page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resend for email, plus a domain you can verify.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Jitsi Meet server for live classes, with its own hosting cost.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An S3-compatible bucket such as Cloudflare R2 for video and files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An AI key, only if you use the assistant.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local work is free. SQLite needs no database server, the checkout simulator charges nobody, and free courses never need a processor.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to customise next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Categories and the catalog.&lt;/strong&gt; Set the catalog page size with the &lt;code&gt;courses_per_page&lt;/code&gt; setting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review moderation.&lt;/strong&gt; Set &lt;code&gt;reviews_require_approval&lt;/code&gt; to true so new reviews wait for a moderator.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Blog.&lt;/strong&gt; The admin has a blog editor, and the student site publishes the posts. It is a quiet way to bring in search traffic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Default language.&lt;/strong&gt; Open in Arabic by setting &lt;code&gt;defaultLocale&lt;/code&gt; to &lt;code&gt;ar&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A third language.&lt;/strong&gt; Add a locale to each app and the API. Course content itself holds exactly two languages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Roles.&lt;/strong&gt; Edit what each role may do on the roles screen and the change reaches signed-in staff.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Skipping the webhook
&lt;/h3&gt;

&lt;p&gt;If students pay and nothing happens, the webhook is the first thing to check. Enrollment follows the processor's confirmation, so a missing subscription leaves orders pending.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Stripe but not PayPal
&lt;/h3&gt;

&lt;p&gt;The checkout always offers card and PayPal. An order paid through a method with no keys uses the simulator and completes without charging anyone. Configure both before you take real orders.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expecting live classes without a meeting server
&lt;/h3&gt;

&lt;p&gt;The template provides the scheduling, tokens and room screens. The meeting server is yours to run. No server means the calendar marks classes as unavailable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Broken course images after connecting a bucket
&lt;/h3&gt;

&lt;p&gt;A frontend shows pictures only from hosts that were listed when it was built. Add your bucket's public host, with no &lt;code&gt;https://&lt;/code&gt;, and rebuild.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treating App Settings as a rebrand
&lt;/h3&gt;

&lt;p&gt;The tab changes the emails only. Names, logos and colors come from files in each app.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I build an LMS with Next.js?
&lt;/h3&gt;

&lt;p&gt;Start from a template that already covers courses, progress, assessment and payment, then adapt the brand and content. Learnio gives you a Next.js 16 student site, an admin dashboard and a NestJS API that already work together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it support live classes?
&lt;/h3&gt;

&lt;p&gt;Yes, on a Jitsi Meet server that you run. Students and instructors join inside the platform, from a course calendar that also shows deadlines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use it for clients?
&lt;/h3&gt;

&lt;p&gt;Yes. The license covers unlimited projects, including ones you hand over to a customer. You cannot resell the source code or release it as a template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it support Arabic and RTL?
&lt;/h3&gt;

&lt;p&gt;Yes. All three apps ship in English and Arabic, and Arabic runs right to left. Course content is entered in both languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a backend?
&lt;/h3&gt;

&lt;p&gt;For a real school, yes. The frontends can run alone on sample data, but accounts, payments and progress need the API, which comes in the Full Stack and Backend API packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I sell courses with PayPal as well as Stripe?
&lt;/h3&gt;

&lt;p&gt;The template includes PayPal integration code and a PayPal webhook route next to Stripe's. Both need your own accounts and keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I offer free courses?
&lt;/h3&gt;

&lt;p&gt;Yes. A course priced at 0 enrolls the student straight away and never reaches a payment processor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can instructors see only their own courses?
&lt;/h3&gt;

&lt;p&gt;Yes. An account linked to an instructor profile is limited to that instructor's courses and their students, and writes outside them are refused.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when a payment fails or is refunded?
&lt;/h3&gt;

&lt;p&gt;The processor's webhook events settle, fail or refund the order. The seat is created only when the payment is confirmed, so a failed payment never grants one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I add another language?
&lt;/h3&gt;

&lt;p&gt;Yes, for the interface. Add the code to the student site's locale list, to the admin's lists and to the API's supported list, then translate the message files. Course content itself holds only English and Arabic.&lt;/p&gt;

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

&lt;p&gt;Open the &lt;a href="https://lms.aniq-ui.com/en" rel="noopener noreferrer"&gt;student website demo&lt;/a&gt; and the &lt;a href="https://admin-lms.aniq-ui.com/en" rel="noopener noreferrer"&gt;admin and instructor demo&lt;/a&gt;. Then read the &lt;a href="https://www.aniq-ui.com/en/templates/next-js-lms-elearning-template/docs" rel="noopener noreferrer"&gt;Learnio documentation&lt;/a&gt; or visit the &lt;a href="https://www.aniq-ui.com/en/templates/next-js-lms-elearning-template" rel="noopener noreferrer"&gt;Learnio product page&lt;/a&gt;. More options are in the &lt;a href="https://www.aniq-ui.com/en/templates/category/education-nextjs-lms-course-platform-templates" rel="noopener noreferrer"&gt;education and LMS templates&lt;/a&gt; category and the &lt;a href="https://www.aniq-ui.com/en/blog/best-nextjs-templates" rel="noopener noreferrer"&gt;best Next.js templates guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Learnio is also one of the ten templates in the &lt;a href="https://www.aniq-ui.com/en/templates/nextjs-templates-bundle" rel="noopener noreferrer"&gt;Next.js Templates Bundle&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Build a SaaS Admin Panel With an AI Assistant in Next.js</title>
      <dc:creator>Aniq UI</dc:creator>
      <pubDate>Sun, 18 Oct 2026 20:43:47 +0000</pubDate>
      <link>https://dev.to/aniqui/how-to-build-a-saas-admin-panel-with-an-ai-assistant-in-nextjs-37a9</link>
      <guid>https://dev.to/aniqui/how-to-build-a-saas-admin-panel-with-an-ai-assistant-in-nextjs-37a9</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.aniq-ui.com/en/blog/build-saas-admin-panel-ai-assistant-nextjs" rel="noopener noreferrer"&gt;Aniq UI blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By the end of this guide you will have an internal admin panel with an AI assistant running on your computer and ready for a server. It is a Next.js dashboard with its own API behind it, and you build it from the Aniq UI Admin Dashboard Template 2 instead of starting from an empty folder.&lt;/p&gt;

&lt;p&gt;The steps work for a SaaS admin panel, a back office for a small company, or any internal tool where a team needs lists, roles and a way to ask questions about their own data. To keep it concrete, we walk one made-up company through the whole job, from the first download to a live address, and we stop at every point where something usually goes wrong.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;About this example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Larkspur Software is an illustrative business we use to show the steps. It is not a client story, and the numbers in it are examples, not results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Before you start: decisions to make
&lt;/h2&gt;

&lt;p&gt;Most of the pain in an internal tool comes from choices made late. Settle these six before you open a terminal, and the rest of the guide goes faster.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who signs in, and what may each person do?&lt;/strong&gt; Write the list of roles on paper first. The template ships five (Super Admin, Admin, Manager, Editor, Viewer) and you can reshape them, but it is easier to reshape with a plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What are your records?&lt;/strong&gt; The template comes with users, projects, tasks and app settings. If your business revolves around something else, such as orders or tickets, decide now whether you will rename what exists or add a new module to the API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Which languages?&lt;/strong&gt; English and Arabic ship. A third language is possible but is more work, so know before you start whether you need it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who pays for AI?&lt;/strong&gt; Either the company holds one provider key on the server, or each person brings their own. The choice changes your bill and your privacy story, and step 8 covers both.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Where will it run?&lt;/strong&gt; A single small server using Docker, or Vercel for the dashboard with the API on a Docker host. Pick the shape early so you set the right addresses once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Which database?&lt;/strong&gt; SQLite is the default and needs no setup. MySQL is a configuration change. A host without a persistent disk is the usual reason to pick MySQL.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The brief
&lt;/h2&gt;

&lt;p&gt;Larkspur Software is a small agency that builds web apps for other companies. The office needs one place to run its work. Here is what the team asks for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A sign-in for staff, with different levels of access: the owner sees everything, developers edit projects, contractors only read.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A list of every client project, with its status, its environment (staging or production) and its current version.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A list of client accounts, with a quick way to find the ones who never verified their email.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An assistant that answers plain questions such as "which projects are blocked?" and shows the answer as a table or a chart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;English and Arabic, because two client teams read Arabic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A first run on a laptop, then a small server on the agency's own domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Control over who can use the assistant, since it can change records and the AI provider bills for usage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the template covers the brief
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;What you use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Staff with different access&lt;/td&gt;
&lt;td&gt;Dashboard and API&lt;/td&gt;
&lt;td&gt;Admin Management and Admin Roles screens; permissions named like projects.edit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client project tracking&lt;/td&gt;
&lt;td&gt;Dashboard and API&lt;/td&gt;
&lt;td&gt;Projects screen with status, environment, version and an English and Arabic name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client accounts&lt;/td&gt;
&lt;td&gt;Dashboard and API&lt;/td&gt;
&lt;td&gt;Users and Deleted users screens, filters, verify and restore actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain-language questions&lt;/td&gt;
&lt;td&gt;Dashboard and API&lt;/td&gt;
&lt;td&gt;AI Assistant screen, with a provider key from you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;English and Arabic&lt;/td&gt;
&lt;td&gt;Dashboard&lt;/td&gt;
&lt;td&gt;/en and /ar addresses, message files under messages/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laptop, then a server&lt;/td&gt;
&lt;td&gt;Docker and configuration&lt;/td&gt;
&lt;td&gt;Docker Compose, .env files, a web server in front&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control over the assistant&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;The ai_chat.use permission on a role&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
&lt;br&gt;
``&lt;code&gt;&lt;/code&gt; ``&lt;/p&gt;

&lt;h2&gt;
  
  
  Template, from scratch or a hosted builder
&lt;/h2&gt;

&lt;p&gt;There are three honest ways to get an admin panel with an AI assistant, and each fits a different team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From scratch.&lt;/strong&gt; You get exactly the structure you want and nothing extra. You also build sign-in, roles with live updates, filtered lists, a chat interface that renders tables and charts, and the API behind all of it. This suits teams with an unusual product and plenty of engineering time. For everyone else it means a long stretch spent on screens that every admin panel needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hosted builder.&lt;/strong&gt; A low-code or internal-tool service gets you a working screen quickly and handles hosting for you. In exchange you work inside its component set and its pricing model, and moving away later usually means rebuilding. Check what it charges per user, what you can export, and whether it supports right-to-left layouts before you commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A template with source code.&lt;/strong&gt; You start with finished screens, a working API and an assistant, and you own the code. You pay once for the template, host it yourself, and change anything you like. The cost is that you must be comfortable with Next.js, and later with NestJS if you extend the API. The license does not let you publish or resell the source, but it does cover unlimited projects, including ones for clients.&lt;/p&gt;

&lt;p&gt;Larkspur has developers on staff and wants to keep client data on its own server, so it takes the template route. If nobody on your team writes code, a hosted builder is the more realistic choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example launch plan by week
&lt;/h2&gt;

&lt;p&gt;This is a sample schedule for Larkspur, not a benchmark. Your own pace will differ.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 1, get it running.&lt;/strong&gt; Download the ZIP, start it with Docker, click through every screen, and try both sample accounts. Decide the six questions above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 2, make it theirs.&lt;/strong&gt; Brand it, rebuild the roles, create the real staff accounts, and delete the sample admins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 3, load the work.&lt;/strong&gt; Enter real projects and client accounts, add provider keys, and let two or three people use the assistant with real questions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 4, harden and ship.&lt;/strong&gt; Set production environment values, deploy to the server, check the health address, and walk through the launch checklist near the end of this post.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step by step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Choose the package
&lt;/h3&gt;

&lt;p&gt;The template comes in three packages. The Full Stack package holds the dashboard and the NestJS API. The Admin Dashboard package is the dashboard alone, running against a small mock server built into it, with the sample records stored in your browser. The Backend API package is the API alone.&lt;/p&gt;

&lt;p&gt;Larkspur wants real data and a real assistant, so it takes Full Stack. If you only want to design screens first, the Admin Dashboard package is enough, and you can point it at an API later. If you already have a front end and only need the server, the Backend API package gives you the routes, the roles logic and the assistant's tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check:&lt;/strong&gt; after the download, your account should show a ZIP named for the package you bought. &lt;strong&gt;Usually goes wrong:&lt;/strong&gt; buying the dashboard package and expecting an API in it. The dashboard alone cannot store anything outside the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Run it locally with Docker
&lt;/h3&gt;

&lt;p&gt;Install Docker Desktop (or Docker Engine on Linux) and nothing else. Then work through these sub-steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2a. Download.&lt;/strong&gt; Open My templates in your Aniq UI account and download the Full Stack ZIP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnojl4m8ds9vxieoqq9m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnojl4m8ds9vxieoqq9m.webp" alt="Documentation video still of downloading the Full Stack ZIP of the Admin Dashboard Template 2" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at which package is chosen on the download page, because the folder names in the later steps depend on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2b. Unzip and open the folder.&lt;/strong&gt; Unpack the archive and start a terminal inside the &lt;code&gt;dashboard-2-full-stack&lt;/code&gt; folder (on Windows, unpack it with File Explorer first).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fom80vrdwv3ze6cu1u7bf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fom80vrdwv3ze6cu1u7bf.webp" alt="Documentation video still of unzipping the Full Stack package and opening its folder in a terminal" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the folder contents: you should see &lt;code&gt;back-end&lt;/code&gt;, &lt;code&gt;front-end&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt; side by side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2c. Start both apps.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
docker compose up --build


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;On the first start Docker builds two images and seeds the database, so give it a few minutes. Later starts take seconds.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdb2xj8arb2vovebkd4i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdb2xj8arb2vovebkd4i.webp" alt="Documentation video still of starting the dashboard and API together with Docker Compose" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the terminal: it prints build progress first, and the dashboard waits until the API reports itself healthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2d. Open it.&lt;/strong&gt; Go to &lt;code&gt;localhost:3030&lt;/code&gt; and use the account &lt;code&gt;admin@example.com&lt;/code&gt; with the password &lt;code&gt;Admin@123&lt;/code&gt;. Arabic lives at &lt;code&gt;/ar&lt;/code&gt;. The API answers at &lt;code&gt;localhost:8000/api/health&lt;/code&gt; once its database is ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; the health address should answer, and the dashboard should list data on its first screen. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a port is already in use. If another program holds 3030 or 8000, create a file named &lt;code&gt;.env&lt;/code&gt; next to &lt;code&gt;docker-compose.yml&lt;/code&gt; and set &lt;code&gt;DASHBOARD_PORT=3040&lt;/code&gt; (or &lt;code&gt;API_PORT&lt;/code&gt; for the API), then run the same command again. A start that seems stuck is normally still seeding sample data.&lt;/p&gt;

&lt;p&gt;To stop the apps, press Ctrl+C. Your data stays in a Docker volume. Be careful with &lt;code&gt;docker compose down -v&lt;/code&gt;: the &lt;code&gt;-v&lt;/code&gt; deletes the volume, and the next start brings back fresh sample data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Know the other ways to run it
&lt;/h3&gt;

&lt;p&gt;You will not use all of these, but each solves a real situation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dashboard alone.&lt;/strong&gt; With the Admin Dashboard package, you build one image and start one container. With no API address set, it runs on its mock server and needs no back end.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dpk9hqw36merogwsqbu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0dpk9hqw36merogwsqbu.webp" alt="Documentation video still of unzipping the Admin Dashboard package and opening its folder" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the folder name in the terminal: this package extracts to a different folder than the Full Stack one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhig6js13syj9ovudy6rz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhig6js13syj9ovudy6rz.webp" alt="Documentation video still of building the Admin Dashboard Docker image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the build output: the dependencies and the build both happen inside the image, so the first build needs an internet connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The API alone.&lt;/strong&gt; With the Backend API package, you build an image and run it with a volume that holds the database file. The first run seeds that database with the sample records and accounts. Every route sits under &lt;code&gt;/api&lt;/code&gt;, and you check it with the health address.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feyami4fx6rxoztjlafk2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feyami4fx6rxoztjlafk2.webp" alt="Documentation video still of unzipping the Backend API package and opening its folder" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the files in the folder, which include a &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;.env.example&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1bkb743f9ivjmxqea9c.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi1bkb743f9ivjmxqea9c.webp" alt="Documentation video still of building the Backend API Docker image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the image being built: the API image is separate from the dashboard's.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv8anu6fvlh9dpc33v2wf.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fv8anu6fvlh9dpc33v2wf.webp" alt="Documentation video still of starting the Backend API container and checking its health" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the health check at the end: a working API answers with an &lt;code&gt;ok&lt;/code&gt; status.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Without Docker.&lt;/strong&gt; Install Node.js 22.9 or newer and run &lt;code&gt;corepack enable&lt;/code&gt; once. Start the API first, because the dashboard reads all its data from it. In &lt;code&gt;back-end&lt;/code&gt;, copy the example settings, create the database with &lt;code&gt;yarn seed&lt;/code&gt;, then run &lt;code&gt;yarn dev&lt;/code&gt;. In &lt;code&gt;front-end&lt;/code&gt;, copy the example settings and start it the same way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh9j2ti5vb2e7wl0z3s64.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh9j2ti5vb2e7wl0z3s64.webp" alt="Documentation video still of starting the API without Docker using Yarn" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the first terminal, which runs the API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4fcwtvi9p0070dlxmr3o.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4fcwtvi9p0070dlxmr3o.webp" alt="Documentation video still of starting the dashboard without Docker in a second terminal" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the second terminal, which runs the dashboard against the API from the first.&lt;/p&gt;

&lt;p&gt;The same approach works for the single-app packages.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcuitor698j3ganbjugne.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcuitor698j3ganbjugne.webp" alt="Documentation video still of starting the Admin Dashboard package on its sample data without Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at how little is needed here: no &lt;code&gt;.env&lt;/code&gt; file means the mock server answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcep0ybf66qn89v0vkzn2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcep0ybf66qn89v0vkzn2.webp" alt="Documentation video still of starting the Backend API package without Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the API terminal staying open while you use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a Node.js older than 22.9, which makes &lt;code&gt;yarn dev&lt;/code&gt; fail on reading &lt;code&gt;.env&lt;/code&gt;; a Yarn that answers with version 1.22 because Corepack is off; and a dashboard that shows sample data even though you meant to use your API, because the API address was missing when it was built.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Explore the sample data before you change anything
&lt;/h3&gt;

&lt;p&gt;The seed loads a small company's worth of records: a dozen admins, five roles carrying 25 permissions, 15 users, 15 projects, 10 tasks and 13 app settings, plus some deleted users and projects to practise restoring. Click through the sidebar from top to bottom, ending on the page that lists every UI component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F73p7o0te1ekr1r5f2ljs.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F73p7o0te1ekr1r5f2ljs.webp" alt="Documentation video still of browsing the Admin Dashboard Template 2 during a tour" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at how the sidebar groups the screens, since you will rename and prune this menu later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6ikd9zl2hdhn6rw5skk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa6ikd9zl2hdhn6rw5skk.jpg" alt="Overview screen of the Admin Dashboard Template 2 with key figures, charts and quick tasks" width="800" height="1186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the top of the overview: key figures, charts, a calendar and quick tasks share one screen, and the widgets can be rearranged.&lt;/p&gt;

&lt;p&gt;Then sign out and sign in as &lt;code&gt;john.smith@admin.com&lt;/code&gt; with the password &lt;code&gt;admin123&lt;/code&gt;. This account has the Viewer role. It can read every list but cannot create, edit or delete anything, and it cannot open the assistant. Seeing that difference early tells you how the permission system behaves, which matters for step 6.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; you can explain, in one sentence each, what Super Admin and Viewer can do. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; people edit sample records first and then forget which data is real. Keep the sample data untouched until you are ready to replace it in bulk.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Put the agency's name on it
&lt;/h3&gt;

&lt;p&gt;All branding sits in the dashboard folder (&lt;code&gt;front-end&lt;/code&gt; in the Full Stack package). Run &lt;code&gt;yarn dev&lt;/code&gt; there while you edit and the page reloads on every save. Four places matter most:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;src/config/brand.config.ts&lt;/code&gt; holds &lt;code&gt;BRAND_CONFIG&lt;/code&gt;: the name, the company, the page metadata and the installed-app details, in English and Arabic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;public/assets/images/logo-for-lightmode.png&lt;/code&gt; and &lt;code&gt;logo-for-darkmode.png&lt;/code&gt; are the two logos. Replace the files and keep the names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;src/styles/theme-variables.css&lt;/code&gt; defines every colour once for light and once for dark. The brand colour is &lt;code&gt;--primaryColor&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;messages/&amp;lt;namespace&amp;gt;/en.json&lt;/code&gt; and &lt;code&gt;ar.json&lt;/code&gt; hold every word on screen. Edit both languages together.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Larkspur changes the name, swaps both logos and picks one new primary colour. That is enough for the dashboard to feel like theirs. Components read colours through variables such as &lt;code&gt;bg-[var(--surface)]&lt;/code&gt;, so any component you add later keeps both themes if it does the same.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb58icej27jccinqyh8lr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb58icej27jccinqyh8lr.jpg" alt="UI component library page of the Admin Dashboard Template 2 with buttons, tables, dialogs and charts" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the component page: it is the quickest way to see your new colour on every kind of control at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; switch between light and dark in the header, then open the component page in both. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a logo with a baked-in background colour that looks wrong in one theme. Export both logos with transparent backgrounds.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Set up staff and roles
&lt;/h3&gt;

&lt;p&gt;The seed ships five roles: Super Admin, Admin, Manager, Editor and Viewer. One person can carry more than one role, and they receive the combined permissions of all of them. Permissions are named &lt;code&gt;module.action&lt;/code&gt;, for example &lt;code&gt;users.view&lt;/code&gt;, &lt;code&gt;projects.edit&lt;/code&gt; or &lt;code&gt;settings.edit&lt;/code&gt;, and there are 25 of them across admins, roles, users, projects, settings and the assistant.&lt;/p&gt;

&lt;p&gt;Larkspur maps its team like this: the owner stays Super Admin, the two project leads become Manager, developers become Editor, and contractors become Viewer. Do it in three passes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open Admin Roles and read what each role grants. Rename or trim until the roles match the paper plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open Admin Management and create each real person, then assign their roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sign in as one person per role and try the actions that role should and should not have.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you change a role while someone is signed in, their menus and buttons update without a reload, because the API tells their dashboard over a WebSocket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnm14ozx25wvn6kzfney2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnm14ozx25wvn6kzfney2.jpg" alt="Admin management screen of the Admin Dashboard Template 2 listing staff accounts with role chips" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the role chips on each account: they show at a glance who holds which role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrdzkapzzlh6vkoh9mlc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrdzkapzzlh6vkoh9mlc.jpg" alt="Roles and permissions screen of the Admin Dashboard Template 2 showing roles and their access policies" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at how permissions are grouped by module, which makes it easy to grant "view" without "edit".&lt;/p&gt;

&lt;p&gt;Before anything else, change the passwords of the sample admins under Settings, Security, or delete those accounts. The seed creates them with passwords printed in the public documentation, and the template's guides warn about this more than once for good reason.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; deleting the Super Admin before another admin can manage roles, or giving the Admin role to everyone because it is easier. Start strict and loosen when someone asks.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Load projects and client accounts
&lt;/h3&gt;

&lt;p&gt;Open Projects and replace the sample rows with Larkspur's real work. A project carries a name and description in English and Arabic, a status (&lt;code&gt;in-progress&lt;/code&gt;, &lt;code&gt;ready&lt;/code&gt; or &lt;code&gt;blocked&lt;/code&gt;), an environment, a version and an optional picture. Deleting is a soft delete: deleted projects move to their own screen and can be restored.&lt;/p&gt;

&lt;p&gt;Users work the same way. You can narrow the list by username, email, phone, name or join date, and the top of the page counts verified, unverified and deleted accounts. For Larkspur, that is the screen for client staff who got an invitation and never confirmed it. The resend-verification route answers success but sends no mail, so connect your own mail service before you rely on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpow3b6sh7ey5e1by8vht.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpow3b6sh7ey5e1by8vht.jpg" alt="User management screen of the Admin Dashboard Template 2 with verified and unverified counts and filters" width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the counts above the table and at the status chips on each row.&lt;/p&gt;

&lt;p&gt;If your own product has other records, the API is plain REST under &lt;code&gt;/api&lt;/code&gt;, so you can add modules beside the existing ones. Sign in with &lt;code&gt;POST /api/auth/login&lt;/code&gt; to get the token every other call needs, and send it as a bearer token. Lists take &lt;code&gt;page&lt;/code&gt; and &lt;code&gt;page_count&lt;/code&gt;, capped at 100 per page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; create a project in English and Arabic, reload, and confirm both names appear when you switch language. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; filling only the English name, which leaves the Arabic list with an empty label.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Turn on the AI assistant
&lt;/h3&gt;

&lt;p&gt;The assistant is its own screen. You type a question in English or Arabic and the answer is built from the dashboard's data, drawn with the same stat cards, tables, lists, donuts and charts the rest of the panel uses. It can also act: add, change, remove or bring back users, and adjust app settings. It does this only inside the permissions of the person asking.&lt;/p&gt;

&lt;p&gt;It runs on Google Gemini, OpenAI or DeepSeek, chosen in a model picker. Conversations are saved per admin in a history sidebar. You have two ways to supply a key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Put the provider's key in &lt;code&gt;back-end/.env&lt;/code&gt; and restart the API (with Docker Compose, run &lt;code&gt;docker compose up&lt;/code&gt; again). Then everyone with access uses your key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leave the server without a key. The assistant then asks each person for their own, and it stays in their browser.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Larkspur starts with the second option, so nobody's usage lands on the agency's bill by accident. The docs say Gemini works with free keys, while OpenAI needs billing enabled on the key. A free key can run out of quota, and the assistant says so; pick another model or wait.&lt;/p&gt;

&lt;p&gt;Control access with the &lt;code&gt;ai_chat.use&lt;/code&gt; permission. Because the assistant can write, give it only to roles you would trust with those writes. Contractors on the Viewer role get none.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcoj1g09ybhdcm7qznfpe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcoj1g09ybhdcm7qznfpe.jpg" alt="AI assistant screen of the Admin Dashboard Template 2 answering a question with a chart" width="799" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at how the reply mixes a written summary with a chart built from the dashboard's own numbers.&lt;/p&gt;

&lt;p&gt;Two optional extras exist. A Cloudflare R2 bucket lets people upload profile pictures and image attachments; without it, uploads answer that they are not set up and everything else works. An MCP key you choose switches on the API's MCP endpoint; without one, that endpoint refuses every request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; ask three questions you already know the answer to, such as how many projects are blocked, and compare. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; the "asks for an API key" dialog, which only means the server has no key for the model you picked. Paste your own, or add the provider's key to the server file and restart.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Check Arabic and the other language details
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;/ar&lt;/code&gt;. The page sets &lt;code&gt;lang="ar"&lt;/code&gt; and &lt;code&gt;dir="rtl"&lt;/code&gt; before it draws, so the sidebar moves to the right and tables and menus read right to left. The API also answers in Arabic when a request sends &lt;code&gt;Accept-Language: ar&lt;/code&gt;, and projects, settings and countries carry both languages in the database.&lt;/p&gt;

&lt;p&gt;Anything you add should use logical Tailwind classes such as &lt;code&gt;ms-2&lt;/code&gt;, &lt;code&gt;pe-4&lt;/code&gt;, &lt;code&gt;start-0&lt;/code&gt; and &lt;code&gt;text-start&lt;/code&gt; instead of &lt;code&gt;ml-*&lt;/code&gt;, &lt;code&gt;pr-*&lt;/code&gt;, &lt;code&gt;left-*&lt;/code&gt; or &lt;code&gt;text-left&lt;/code&gt;, or the Arabic page comes out wrong. If a third language is ever needed, you add its code in three locale lists, copy each &lt;code&gt;en.json&lt;/code&gt; file under the new code, and add it to the language menu. The API keeps answering a new language in English until you add its translations in &lt;code&gt;back-end/src/i18n/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; walk every screen you changed in Arabic, including the assistant. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a text added to &lt;code&gt;en.json&lt;/code&gt; only, so the Arabic page shows a missing key.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Go live on a server
&lt;/h3&gt;

&lt;p&gt;The same &lt;code&gt;docker-compose.yml&lt;/code&gt; that ran on the laptop also runs on a server. Give the dashboard and the API each a domain behind Caddy or Nginx, which adds HTTPS, and route the first domain to port 3030 (the dashboard) and the second to port 8000 (the API).&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.env&lt;/code&gt; beside &lt;code&gt;docker-compose.yml&lt;/code&gt; and set &lt;code&gt;DASHBOARD_URL&lt;/code&gt; and &lt;code&gt;API_URL&lt;/code&gt; to the two public addresses. The dashboard bakes the API's address into its built code, and the API takes the same two lines for &lt;code&gt;CORS_ORIGIN&lt;/code&gt; and &lt;code&gt;FRONTEND_URL&lt;/code&gt;. Put your AI keys and &lt;code&gt;TRUST_PROXY=1&lt;/code&gt; in &lt;code&gt;back-end/.env&lt;/code&gt;. Then start it in the background:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
docker compose up --build -d


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In production the API refuses to start with the example &lt;code&gt;JWT_SECRET&lt;/code&gt; or an empty &lt;code&gt;CORS_ORIGIN&lt;/code&gt;. With Docker it generates a secret for you and keeps it in the data volume, or you can make your own:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it is up, open &lt;code&gt;https://api.your-domain.com/api/health&lt;/code&gt; (with your own domain) and look for &lt;code&gt;"status":"ok"&lt;/code&gt;. If you prefer, the dashboard alone can go to Vercel from a private Git repository: set &lt;code&gt;ENABLE_EXPERIMENTAL_COREPACK&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;, then &lt;code&gt;NEXT_PUBLIC_API_BASE_URL&lt;/code&gt;, &lt;code&gt;NEXT_PUBLIC_WEBSOCKET_BASE_URL&lt;/code&gt; and &lt;code&gt;NEXT_PUBLIC_BASE_URL&lt;/code&gt;. Redeploy after changing any &lt;code&gt;NEXT_PUBLIC_*&lt;/code&gt; value, because it is compiled in at build time. Leave &lt;code&gt;NEXT_PUBLIC_DEMO_MODE&lt;/code&gt; off unless the site is a public demo.&lt;/p&gt;

&lt;p&gt;The default database is SQLite, kept in the &lt;code&gt;/data&lt;/code&gt; volume. MySQL is a configuration change: create an empty database, set &lt;code&gt;DB_TYPE&lt;/code&gt; and the connection in &lt;code&gt;back-end/.env&lt;/code&gt;, and seed it. Pick MySQL if your host has no persistent disk. If you run the API without Docker in production, &lt;code&gt;yarn seed:prod&lt;/code&gt; creates the tables once; run it a single time, because a second run resets app settings and the built-in roles' permissions to their sample values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; sign in from a different computer, change a role, and watch a second signed-in session update. That proves the WebSocket address is right. &lt;strong&gt;What usually goes wrong:&lt;/strong&gt; empty pages with a CORS line in the browser console. The API only answers browsers from the addresses in &lt;code&gt;CORS_ORIGIN&lt;/code&gt;, so add the dashboard's real address there and to &lt;code&gt;FRONTEND_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it day to day
&lt;/h2&gt;

&lt;p&gt;Once Larkspur is live, the work becomes small, repeatable routines.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Onboarding a person.&lt;/strong&gt; Create the admin in Admin Management, assign one role, and send them the address. Do not share the Super Admin account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Offboarding.&lt;/strong&gt; Delete the admin account the same day. Because roles update live, you can also strip a role from someone who is still signed in and their buttons disappear without a reload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Changing what a role may do.&lt;/strong&gt; Edit it on the Admin Roles screen and tell the team. Test with a real account for that role.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Restoring mistakes.&lt;/strong&gt; Users and projects are soft deleted. Open the matching Deleted screen to bring one back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backups.&lt;/strong&gt; With SQLite in Docker, the data lives in one volume. Back that volume up on a schedule that suits how often records change. With MySQL, use your database host's backup tool.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Updating.&lt;/strong&gt; After you change code, build the images again, because a running container is a snapshot of the code at build time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Watching the assistant.&lt;/strong&gt; Review who has &lt;code&gt;ai_chat.use&lt;/code&gt; now and then.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it costs to run
&lt;/h2&gt;

&lt;p&gt;The template is a one-time purchase from $99, and the package matters: the Full Stack package that this guide uses costs more than the entry price, and the product page lists each one. The licence covers an unlimited number of projects, client work included, as long as you do not share the source code publicly.&lt;/p&gt;

&lt;p&gt;Beyond that, you pay for what you add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hosting for the dashboard and the API. A Docker-capable server runs both; Vercel can host the dashboard alone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AI usage, if you put a provider key on the server. Google, OpenAI and DeepSeek bill by their own pricing pages. If each person brings their own key, the bill is theirs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Cloudflare R2 bucket, if you want picture uploads, and a WeatherAPI.com key if you want the weather in the greeting. Both are optional.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A domain name and, if you use it, a mail service for verification emails.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is free: local development on SQLite, the sample data, the built-in mock API in the dashboard package, and every optional feature you leave switched off.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to customise next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Roles that match your company.&lt;/strong&gt; Rename the five roles and trim their permissions until each one grants only what the job needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your own records.&lt;/strong&gt; Add a module to the NestJS API for something Larkspur tracks that is not a project, such as invoices or support tickets, and a screen to match. Each domain in the API has its own module, so a new one slots in beside the others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;New assistant tools.&lt;/strong&gt; The assistant routes through an MCP registry that is built to be extended, so you can teach it questions specific to your data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A third language.&lt;/strong&gt; Copy the message files, add the code to the three locale lists, and add the API's translations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleaner production code.&lt;/strong&gt; Once your own API is the permanent source of data, the &lt;code&gt;yarn remove:mock&lt;/code&gt; script deletes the built-in mock server, and &lt;code&gt;yarn remove:demo&lt;/code&gt; removes the public-demo switch. Commit first, since both rewrite files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Launch checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sample admins deleted or their passwords changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Your own &lt;code&gt;JWT_SECRET&lt;/code&gt; set, or Docker's generated one confirmed in the data volume.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CORS_ORIGIN&lt;/code&gt; and &lt;code&gt;FRONTEND_URL&lt;/code&gt; hold the dashboard's real address.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;NEXT_PUBLIC_API_BASE_URL&lt;/code&gt; points at the API with &lt;code&gt;/api&lt;/code&gt;, and the dashboard was built after you set it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTPS in front of both the dashboard and the API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The health address answers &lt;code&gt;"status":"ok"&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every role tested with a real account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ai_chat.use&lt;/code&gt; granted only where intended, and provider keys placed where you decided.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Arabic checked on every screen you changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Data volume or database backup scheduled.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Demo mode off.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Going live with the sample admins
&lt;/h3&gt;

&lt;p&gt;Every seed creates admins with published passwords. Change them or delete them before anyone else can reach the panel, and set your own &lt;code&gt;JWT_SECRET&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing a NEXT_PUBLIC value and expecting a change
&lt;/h3&gt;

&lt;p&gt;Those values are compiled into the dashboard when it is built. Rebuild (or restart &lt;code&gt;yarn dev&lt;/code&gt;) after any edit, and never put a secret in one: anyone can read them in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiping data with the wrong stop command
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docker compose down&lt;/code&gt; keeps your data. Adding &lt;code&gt;-v&lt;/code&gt; removes the volume, and everything you created goes with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pages stay empty and the console mentions CORS
&lt;/h3&gt;

&lt;p&gt;The API only answers browsers from the addresses in &lt;code&gt;CORS_ORIGIN&lt;/code&gt;. If the dashboard runs on a new domain or port, add it there and in &lt;code&gt;FRONTEND_URL&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handing the assistant to everyone
&lt;/h3&gt;

&lt;p&gt;The assistant can change users and settings. Grant &lt;code&gt;ai_chat.use&lt;/code&gt; to the roles that should have that power and nobody else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing the example environment file to Docker
&lt;/h3&gt;

&lt;p&gt;The API image runs in production mode. If you start it with a copy of the example environment file, that file sets development mode over the image's own value. Pass each value you need with &lt;code&gt;-e&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I build an admin panel with an AI assistant in Next.js?
&lt;/h3&gt;

&lt;p&gt;Start from a dashboard that already has the roles, lists and chat screen, then connect your data and a model provider. In this template, the assistant reads from the same API as the rest of the panel, so you add a key and your records instead of writing chat plumbing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a backend?
&lt;/h3&gt;

&lt;p&gt;Only for real data. The Admin Dashboard package runs on a mock server inside the browser, which is enough for design work. For a live panel you need an API, and the Full Stack package includes a NestJS one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI providers does the assistant support?
&lt;/h3&gt;

&lt;p&gt;Google Gemini, OpenAI and DeepSeek, picked from a model list. Each person can paste their own key, or you can set one key on the server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it support Arabic and RTL?
&lt;/h3&gt;

&lt;p&gt;Yes. Every screen, including the assistant, exists in English and Arabic, and the Arabic pages are laid out right to left.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use it for clients?
&lt;/h3&gt;

&lt;p&gt;Yes. The commercial licence covers unlimited projects, including work you build for a client and hand over. You may not publish the source code or resell it as a template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I change what the assistant is allowed to do?
&lt;/h3&gt;

&lt;p&gt;You decide who may use it through the &lt;code&gt;ai_chat.use&lt;/code&gt; permission, and the assistant acts only within that person's own permissions. Adding new tools is done through its MCP registry in the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I try it without installing anything?
&lt;/h3&gt;

&lt;p&gt;Yes. The live demo runs the same dashboard. For your own machine, the Admin Dashboard package runs on its built-in sample data with no API or AI key at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I remove the sample data and the mock server?
&lt;/h3&gt;

&lt;p&gt;Yes. Two scripts, &lt;code&gt;yarn remove:mock&lt;/code&gt; and &lt;code&gt;yarn remove:demo&lt;/code&gt;, strip the mock server and the public-demo switch from the code. Commit first, since they rewrite files.&lt;/p&gt;

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

&lt;p&gt;Open the &lt;a href="https://dashboard-2.aniq-ui.com" rel="noopener noreferrer"&gt;live demo&lt;/a&gt; to click through the dashboard and the assistant. The &lt;a href="https://www.aniq-ui.com/en/templates/next-js-admin-dashboard-template-2" rel="noopener noreferrer"&gt;Admin Dashboard Template 2 product page&lt;/a&gt; lists the packages, and the &lt;a href="https://www.aniq-ui.com/en/templates/next-js-admin-dashboard-template-2/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; covers every step above in detail. More options live in the &lt;a href="https://www.aniq-ui.com/en/templates/category/dashboard-nextjs-admin-panel-templates" rel="noopener noreferrer"&gt;admin dashboard templates category&lt;/a&gt;, and the &lt;a href="https://www.aniq-ui.com/en/blog/admin-dashboard-templates-by-industry" rel="noopener noreferrer"&gt;guide to admin dashboard templates by industry&lt;/a&gt; helps if your use case differs from this one.&lt;/p&gt;

&lt;p&gt;This template is also one of the ten in the &lt;a href="https://www.aniq-ui.com/en/templates/nextjs-templates-bundle" rel="noopener noreferrer"&gt;Next.js Templates Bundle&lt;/a&gt;, if you expect to build more than one kind of product.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Build an Online Store with an Admin Panel in Next.js</title>
      <dc:creator>Aniq UI</dc:creator>
      <pubDate>Sun, 18 Oct 2026 20:42:57 +0000</pubDate>
      <link>https://dev.to/aniqui/how-to-build-an-online-store-with-an-admin-panel-in-nextjs-4788</link>
      <guid>https://dev.to/aniqui/how-to-build-an-online-store-with-an-admin-panel-in-nextjs-4788</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.aniq-ui.com/en/blog/build-online-store-admin-panel-nextjs" rel="noopener noreferrer"&gt;Aniq UI blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By the end of this guide you will know how to build an online store with an admin panel, using Next.js, NestJS and the e-commerce template from Aniq UI. You will have a storefront, an order desk for your team, and an API that holds the catalogue, customers and orders.&lt;/p&gt;

&lt;p&gt;We follow one example shop from its first plan to launch day. Each step names the screen or setting involved and uses the exact commands and file paths from the documentation. The same path works for a founder launching a store, or an agency building one for a client.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;About this example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cedar &amp;amp; Cloth is an illustrative business we use to show the steps. It is not a client story, and the numbers in it are examples, not results.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The brief
&lt;/h2&gt;

&lt;p&gt;Cedar &amp;amp; Cloth is a small shop that sells linen shirts and canvas bags. The owner runs it with two people who pack orders. Here is what the shop needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A catalogue of about thirty products, each with sizes and colors, written in English and in Arabic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Guest checkout, so a first-time visitor can buy without making an account, and track the order afterward.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment by card, or cash on delivery for customers who prefer it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Free shipping above a set order total, and a flat fee below it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An order desk where the owner has full control and the packers can only view orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A home page the owner can rearrange without touching code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clean product photos on a plain background, made from phone shots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A way to see where visitors come from, using Google Analytics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How the template covers the brief
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Where it lives&lt;/th&gt;
&lt;th&gt;What you use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bilingual catalogue with variants&lt;/td&gt;
&lt;td&gt;Admin dashboard and API&lt;/td&gt;
&lt;td&gt;Products and Categories screens, with size and color variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guest checkout and tracking&lt;/td&gt;
&lt;td&gt;Storefront&lt;/td&gt;
&lt;td&gt;The checkout page and Track Order, which asks for order number and email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Card or cash on delivery&lt;/td&gt;
&lt;td&gt;Storefront and API&lt;/td&gt;
&lt;td&gt;Stripe for cards, cash on delivery selected by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shipping rule&lt;/td&gt;
&lt;td&gt;Storefront and API&lt;/td&gt;
&lt;td&gt;A shipping config file and the orders service, kept in sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Order desk and permissions&lt;/td&gt;
&lt;td&gt;Admin dashboard&lt;/td&gt;
&lt;td&gt;Orders, Admin Management and Admin Roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editable home page&lt;/td&gt;
&lt;td&gt;Admin dashboard&lt;/td&gt;
&lt;td&gt;The Home Page screen, section by section&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product photos&lt;/td&gt;
&lt;td&gt;Admin dashboard&lt;/td&gt;
&lt;td&gt;The product form's AI studio and background removal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visitor analytics&lt;/td&gt;
&lt;td&gt;Storefront&lt;/td&gt;
&lt;td&gt;Tag ids pasted into the storefront's environment file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Before you start: decisions to make
&lt;/h2&gt;

&lt;p&gt;A store touches money, customers and legal duties, so a few decisions are worth making before you write a line of configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  What exactly are you selling?
&lt;/h3&gt;

&lt;p&gt;Count your products, and for each one decide which options a shopper chooses: size, color, or neither. Variants carry their own SKU, price adjustment and stock, so the shape of your catalogue decides how much data entry you face. Cedar &amp;amp; Cloth sells shirts in several sizes and colors and bags in one size.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which languages will you sell in?
&lt;/h3&gt;

&lt;p&gt;English and Arabic ship ready, including a right-to-left layout. Products and categories hold a name and description in both. A third language is possible for the interface, but it takes edits in several files.&lt;/p&gt;

&lt;h3&gt;
  
  
  How will shoppers pay?
&lt;/h3&gt;

&lt;p&gt;Shoppers can pay by card (Stripe) or choose cash on delivery, and PayPal is not part of the checkout. Stripe charges in US dollars. If you sell in another currency, that is a decision to settle before launch, not after.&lt;/p&gt;

&lt;h3&gt;
  
  
  What about shipping and tax?
&lt;/h3&gt;

&lt;p&gt;The template ships one shipping rule: free from a set order total, a flat fee below it. It applies no tax. If your business must charge tax, plan for that in code or in how you price products.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guests, accounts, or both?
&lt;/h3&gt;

&lt;p&gt;Both work. Guests check out with an email and track orders by number and email. Customers with accounts also get saved addresses, order history and a wishlist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Who handles email?
&lt;/h3&gt;

&lt;p&gt;The template sends none: no confirmations, no shipping notices, no password resets. Choose a mail provider early, because customers expect at least an order confirmation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which AI tools, if any?
&lt;/h3&gt;

&lt;p&gt;Each one needs its own key, and some need the media bucket. Decide which are worth turning on for launch. A shop that only needs clean product photos can enable background removal and nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Choose the package
&lt;/h3&gt;

&lt;p&gt;The template has four packages: Full Stack (storefront, admin and API), Storefront alone, Admin Dashboard alone, and Backend API alone. The two frontends can each run by themselves on a built-in sample store inside the browser. That is useful for a design pass, but real orders, payments and AI features need the NestJS API.&lt;/p&gt;

&lt;p&gt;Cedar &amp;amp; Cloth takes the Full Stack package. The commercial license covers unlimited projects, including stores you build and hand over to a client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fytujeinsphk582b13fqm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fytujeinsphk582b13fqm.webp" alt="Documentation video still of the Next.js e-commerce template: downloading the ZIP for your package" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The My templates page lists a ZIP for each package, so choose the one you bought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; the Full Stack ZIP extracts to &lt;code&gt;e-commerce-1&lt;/code&gt;, containing &lt;code&gt;admin-dashboard&lt;/code&gt;, &lt;code&gt;back-end&lt;/code&gt;, &lt;code&gt;storefront&lt;/code&gt; and &lt;code&gt;docker-compose.yml&lt;/code&gt;. Single-app packages extract to their own folder with a &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;.env.example&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Run it locally with Docker
&lt;/h3&gt;

&lt;p&gt;Download the ZIP from My templates, unzip it, and open a terminal in the &lt;code&gt;e-commerce-1&lt;/code&gt; folder. Start Docker, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker builds three images on the first run and loads a demo store into the database, so expect a few minutes. The storefront opens at &lt;code&gt;localhost:3030&lt;/code&gt;, the admin at &lt;code&gt;localhost:3031&lt;/code&gt;, and the API at &lt;code&gt;localhost:8000&lt;/code&gt;. A busy port can be moved with &lt;code&gt;SITE_PORT&lt;/code&gt;, &lt;code&gt;ADMIN_PORT&lt;/code&gt; or &lt;code&gt;API_PORT&lt;/code&gt; in a &lt;code&gt;.env&lt;/code&gt; file next to &lt;code&gt;docker-compose.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sign in to the admin as the Super Admin (&lt;code&gt;admin@example.com&lt;/code&gt;, password &lt;code&gt;Admin@123&lt;/code&gt;) and to the storefront as a demo customer (&lt;code&gt;john.doe@example.com&lt;/code&gt;, password &lt;code&gt;password123&lt;/code&gt;). A read-only staff account, &lt;code&gt;john.smith@admin.com&lt;/code&gt; with &lt;code&gt;admin123&lt;/code&gt;, shows what the Viewer role sees. Place a test order as the customer, then find it in the admin.&lt;/p&gt;

&lt;p&gt;One honest note: on the admin overview, several cards (revenue, sales, goal, visitors, conversion and the map) display placeholder figures until you connect your own numbers. Check which cards are wired to real data before you rely on them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmkbumnedzl5h4trdemp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbmkbumnedzl5h4trdemp.webp" alt="Documentation video still of the Next.js e-commerce template: unzipping the download and opening the folder" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This still shows a terminal opened inside the extracted folder, which is where each command should run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F622v6vit3swb2wn4dvmy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F622v6vit3swb2wn4dvmy.webp" alt="Documentation video still of the Next.js e-commerce template: starting the API, the storefront and the admin dashboard with Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look for the API passing its health check, since the two frontends start only after it does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzbbfke4bt2lwpxoot86.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzzbbfke4bt2lwpxoot86.webp" alt="Documentation video still of the Next.js e-commerce template: starting the API without Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Node.js route starts with the API, after the seed has created the database and demo store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1zx4v6kyq2knxb82gte7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1zx4v6kyq2knxb82gte7.webp" alt="Documentation video still of the Next.js e-commerce template: starting the admin dashboard without Docker" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The admin dashboard runs in its own terminal on port 3031, beside the storefront on 3030.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06o5e52p62pwvzf70h7m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F06o5e52p62pwvzf70h7m.jpg" alt="Admin dashboard overview in the Next.js e-commerce template, light theme" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The screenshot above is the admin overview you should see after signing in as the Super Admin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; open &lt;code&gt;localhost:8000/api/health&lt;/code&gt;, which should answer with a status of ok. Then add a product to the cart as the demo customer, check out as a guest, and find the order in the admin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker is not running, or port 3030, 3031 or 8000 is taken. Free it, or set &lt;code&gt;SITE_PORT&lt;/code&gt;, &lt;code&gt;ADMIN_PORT&lt;/code&gt; or &lt;code&gt;API_PORT&lt;/code&gt; to a free number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The first start seems stuck. It is seeding the demo store, and the frontends start only when the API is healthy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A "Sample data" notice shows. The frontend was built without an API address and is running on its own sample store.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yarn reports version 1.22. Run &lt;code&gt;corepack enable&lt;/code&gt; and install again.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Make it Cedar &amp;amp; Cloth
&lt;/h3&gt;

&lt;p&gt;The brand lives in files, and the storefront and the admin keep their own copies. In the storefront, &lt;code&gt;src/config/brand.config.ts&lt;/code&gt; holds the store name (it ships as a text wordmark), the browser tab name, the contact details and the social links, each with English and Arabic values. Set &lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; to your real domain, because canonical links, the sitemap and sharing cards use it.&lt;/p&gt;

&lt;p&gt;The shipped copy describes a menswear shop. Three places carry that copy: the text blocks in &lt;code&gt;brand.config.ts&lt;/code&gt;, the &lt;code&gt;messages/home/&lt;/code&gt; folder for the home page, and &lt;code&gt;messages/seo/&lt;/code&gt; for page titles. Cedar &amp;amp; Cloth sells menswear too, so it edits less than most shops will.&lt;/p&gt;

&lt;p&gt;Each app keeps its colors in &lt;code&gt;src/styles/theme-variables.css&lt;/code&gt;, as CSS variables, with a block for light and a block for dark. The accent is &lt;code&gt;--primaryColor&lt;/code&gt;. The storefront loads Plus Jakarta Sans for Latin text and two Arabic fonts. The admin has its own brand file and uses the system font stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; rebuild, then look for the old name in the browser tab, the footer, the sharing preview and the Arabic pages. Replace the placeholder contact details, since they ship as placeholders.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; the footer shows no social icons, because the links ship empty and only filled ones appear. Another is a sharing image that still shows the template's hero photo. Point &lt;code&gt;meta.ogImage&lt;/code&gt; at your own picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Add categories and products
&lt;/h3&gt;

&lt;p&gt;Start with categories: a name and description in both languages, plus an image, a slug, a sort position and an on/off switch. They are one flat level. Then add products. A product takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A name and descriptions in both languages (only the English name is mandatory), a slug, a SKU and a category.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A price, an optional compare-at price, a cost price and a stock count.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Up to 12 images and videos in any order, one marked primary, each with alt text.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Variants such as Size with M, or Colour with its hex code. Each variant has its own SKU, price adjustment and stock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flags for active, featured and best seller, and free tags.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Uploads go to a Cloudflare R2 bucket, or another S3-compatible store, and fail with a 503 until you fill in the five &lt;code&gt;R2_*&lt;/code&gt; variables in &lt;code&gt;back-end/.env&lt;/code&gt;. The demo catalogue itself renders from bundled files, so you can build the whole flow before you set a bucket.&lt;/p&gt;

&lt;p&gt;Work in this order, so nothing points at something that does not exist yet:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create categories first, since every product needs one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add one complete product, with photos, variants and both languages, before you add the rest.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open it on the storefront in English and Arabic and check the gallery, the variant picker and the price.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only then repeat for the remaining products.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hwtt3j5fnmmowypnfib.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1hwtt3j5fnmmowypnfib.jpg" alt="Products management screen in the Next.js e-commerce admin dashboard, light theme" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqymhqa0kf3h90h4b1ti.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Foqymhqa0kf3h90h4b1ti.jpg" alt="Categories screen in the Next.js e-commerce admin dashboard, light theme" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the flat list of categories with their order and active switch: the sort order here shapes the categories row on the home page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjfrtz6knrl9p5nfwwa3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcjfrtz6knrl9p5nfwwa3.jpg" alt="Products management in compact view, Next.js e-commerce admin dashboard" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The compact view fits more rows on screen, which helps when you are checking stock and prices across many products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; deleted products have their own list, so check where one lands after you remove it. Variant SKUs must be unique, so use a clear naming pattern from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; uploads fail with a 503, because the bucket variables are not set. Or images load at full size, because the frontends were not told your bucket's public host. Set &lt;code&gt;NEXT_PUBLIC_MEDIA_HOSTNAME&lt;/code&gt; to that host, without &lt;code&gt;https://&lt;/code&gt;, and rebuild.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Arrange the home page
&lt;/h3&gt;

&lt;p&gt;The Home Page screen in the admin decides, one section at a time, what shoppers see on the storefront's front page. Categories need only an order. The other sections take a capped number of products or texts: up to 3 style pillars, 2 editorial panels, 6 new drops, 8 best sellers, 6 spotlight items and 5 slides in the closing slider. If New drops is empty, it shows the newest products. Editing a section needs the &lt;code&gt;products.edit&lt;/code&gt; permission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; add a product that uses every section, then open the storefront home page and confirm each section shows what you picked. Empty the New drops section and confirm it falls back to the newest products.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a change does not appear because you lack the &lt;code&gt;products.edit&lt;/code&gt; permission, or because the storefront is on its sample store and not reading the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Set shipping, payments and the order flow
&lt;/h3&gt;

&lt;p&gt;The checkout offers card through Stripe, and cash on delivery. Cash on delivery is selected by default, and the API always prices an order from its own catalogue, never from numbers the browser sends. There is no PayPal option.&lt;/p&gt;

&lt;p&gt;The shipping rule ships as free from $75 and $9.99 below that, with no tax. It is written in two places, &lt;code&gt;storefront/src/config/shipping.ts&lt;/code&gt; and &lt;code&gt;back-end/src/modules/orders/orders.service.ts&lt;/code&gt;. Edit both in one change, or the cart and the charge will disagree.&lt;/p&gt;

&lt;p&gt;To take cards, use Stripe test keys first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add both &lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt; and &lt;code&gt;STRIPE_WEBHOOK_SECRET&lt;/code&gt; to &lt;code&gt;back-end/.env&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Put &lt;code&gt;NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY&lt;/code&gt; in the storefront's &lt;code&gt;.env&lt;/code&gt; and rebuild.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the Stripe dashboard, create a webhook endpoint that points to your API address plus &lt;code&gt;/api/payments/webhook&lt;/code&gt;, and have it listen for two events: &lt;code&gt;payment_intent.succeeded&lt;/code&gt; and &lt;code&gt;payment_intent.payment_failed&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A card order stays pending until Stripe calls that webhook, then becomes paid and confirmed. For local testing, the Stripe CLI can relay events to &lt;code&gt;http://localhost:8000/api/payments/webhook&lt;/code&gt;. In the admin, an order moves one step at a time: pending, confirmed, processing, shipped, delivered, or cancelled before it closes. Add the tracking number when you ship, and download any order as a CSV file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk461crlthkq9h831aubh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk461crlthkq9h831aubh.jpg" alt="Orders list with fulfilment progress in the Next.js e-commerce admin dashboard" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; place a cash on delivery order and a card order with Stripe test keys. The first should be confirmed and unpaid. The second should be pending until the webhook arrives, then paid and confirmed. Fail a card on purpose and confirm the order is marked failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The card option is missing at checkout. The publishable key is empty or not a real test or live key, which leaves cash on delivery as the only choice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Card orders stay pending. The webhook endpoint or its signing secret is wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The storefront still shows the old publishable key. It is compiled in, so rebuild after changing it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 7: Add staff and set their permissions
&lt;/h3&gt;

&lt;p&gt;Staff accounts live under Admin Management, and their permissions under Admin Roles. The seed gives you a Super Admin with every permission and a Viewer role with every view permission, which leaves out the AI assistant. The Admin, Manager and Editor roles exist but start with nothing, so tick their permissions before you give them to anyone.&lt;/p&gt;

&lt;p&gt;Cedar &amp;amp; Cloth keeps the owner as Super Admin. For the two packers it gives view access to orders, plus the edit permission that moving an order forward requires. Everything else stays hidden from them, because the sidebar and the API check the same permission names.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgclavyrcrvklb1s6tq2m.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgclavyrcrvklb1s6tq2m.jpg" alt="Roles and permissions screen in the Next.js e-commerce admin dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; sign in as the Viewer account and confirm it can see lists but not change them. Then sign in as the packer role you created and confirm moving an order forward works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a new staff member sees almost nothing, because their role has no permissions ticked. Permission changes reach signed-in staff over a live connection, so they do not need to sign out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Add the AI tools you want
&lt;/h3&gt;

&lt;p&gt;Each AI feature switches on only when its key is in &lt;code&gt;back-end/.env&lt;/code&gt;, and a key still holding its example value counts as unset.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Admin assistant:&lt;/strong&gt; needs a Google Gemini key. It answers questions about the shop, finds orders, edits products and stock, and draws sales charts, all within the signed-in admin's permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI product studio:&lt;/strong&gt; in the product form. Its assistant mode needs a Gemini key and the media bucket. Its Animate mode needs a Higgsfield key and secret.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fitting room:&lt;/strong&gt; lets shoppers see a product worn on a model or on their own photo. It needs a Gemini key and the bucket.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Background removal:&lt;/strong&gt; needs a model file on your own server and the bucket, but no key, and it costs nothing per image.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cedar &amp;amp; Cloth turns on only background removal, so the phone shots become clean catalogue photos. The Docker image leaves the model out unless you ask for it when you build. Provider charges for the keyed features are yours.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ivh90sxk516o4g84g14.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0ivh90sxk516o4g84g14.jpg" alt="AI assistant sales report in the Next.js e-commerce admin dashboard, light theme" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; open the AI Assistant page and ask for a sales summary. Confirm the answer comes with a chart drawn from your own data. For background removal, open a product photo and confirm the Remove background button appears.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a feature reports itself off. Either its key is missing or still holds the example value, or it needs the media bucket as well. The studio, the fitting room and background removal all need the bucket. Also remember that the assistant and the studio are unavailable on the sample store.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Wire up analytics
&lt;/h3&gt;

&lt;p&gt;Six ad and analytics networks are built into the storefront. Paste an id into &lt;code&gt;storefront/.env&lt;/code&gt; and rebuild. A blank id loads no script. The variables are &lt;code&gt;NEXT_PUBLIC_GTM_ID&lt;/code&gt;, &lt;code&gt;NEXT_PUBLIC_GA4_MEASUREMENT_ID&lt;/code&gt;, &lt;code&gt;NEXT_PUBLIC_META_PIXEL_ID&lt;/code&gt;, &lt;code&gt;NEXT_PUBLIC_TIKTOK_PIXEL_ID&lt;/code&gt;, &lt;code&gt;NEXT_PUBLIC_SNAPCHAT_PIXEL_ID&lt;/code&gt; and &lt;code&gt;NEXT_PUBLIC_PINTEREST_TAG_ID&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you set only one, make it Google Tag Manager, which can load the others itself. If GA4 already runs inside your Tag Manager container, leave the GA4 variable blank, or every session counts twice. The storefront reports page views, product views, add to cart, begin checkout, purchase, search and sign-up. Set &lt;code&gt;NEXT_PUBLIC_ANALYTICS_DEBUG=true&lt;/code&gt; to log each event in the browser console while testing.&lt;/p&gt;

&lt;p&gt;The tags load as soon as a page does. If you sell to the EU or the UK, show a consent banner before they run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check:&lt;/strong&gt; turn on debug logging, click through a purchase and watch the events appear in the browser console in order: product view, add to cart, begin checkout, purchase. Then confirm the same events in Tag Manager's preview or your analytics tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt; a change to an id has no effect, since ids are compiled in and need a rebuild. Or the purchase event fires without the right currency. Set &lt;code&gt;NEXT_PUBLIC_ANALYTICS_CURRENCY&lt;/code&gt; if you do not sell in US dollars.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10: Go live
&lt;/h3&gt;

&lt;p&gt;Fix the gaps the template leaves open, then deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It sends no email at all: no order confirmation, no shipping notice, no password reset. Connect a mail provider in the &lt;code&gt;forgotPassword&lt;/code&gt; hook of &lt;code&gt;back-end/src/modules/customer-auth/customer-auth.controller.ts&lt;/code&gt; and for orders.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Both the contact form and the newsletter signup acknowledge the visitor yet deliver nothing, and no terms or privacy pages come with the template. Connect the forms and write the pages.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then configure the API in &lt;code&gt;back-end/.env&lt;/code&gt;: a fresh &lt;code&gt;JWT_SECRET&lt;/code&gt; (made with &lt;code&gt;node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"&lt;/code&gt;), &lt;code&gt;NODE_ENV=production&lt;/code&gt;, &lt;code&gt;CORS_ORIGIN&lt;/code&gt; listing the storefront and admin addresses, and &lt;code&gt;FRONTEND_URL&lt;/code&gt; set to the admin's address. Build the API, run &lt;code&gt;yarn db:sync:prod&lt;/code&gt; once to create the tables, and start it. In each frontend set &lt;code&gt;NEXT_PUBLIC_API_BASE_URL&lt;/code&gt; with &lt;code&gt;/api&lt;/code&gt; on the end and rebuild. Set &lt;code&gt;NEXT_PUBLIC_STOREFRONT_URL&lt;/code&gt; in the admin so its Go to storefront link works.&lt;/p&gt;

&lt;p&gt;Do not use &lt;code&gt;yarn railway:setup&lt;/code&gt; for a live shop: it drops every table each time it runs. Change the published demo passwords, or start from empty tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to check after going live:&lt;/strong&gt; place a real small order on your domain, watch it appear in the admin with a notification, move it through its statuses, and track it as a guest with the order number and email. Confirm the notification bell is live, which means the admin's address matches &lt;code&gt;FRONTEND_URL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What usually goes wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Browsers are blocked by the API. &lt;code&gt;CORS_ORIGIN&lt;/code&gt; must list both frontends exactly as the browser shows them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Everyone gets "Too many attempts" behind a proxy. Each of those routes (sign-in, registration, password reset, guest tracking) accepts ten requests a minute from one address. Set &lt;code&gt;TRUST_PROXY&lt;/code&gt; to how many proxies stand in front.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Password reset goes nowhere. In production the reset token is not delivered until you connect a mail provider.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running it day to day
&lt;/h2&gt;

&lt;p&gt;When the shop is live, the owner and packers use a short list of admin screens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting the day on the overview
&lt;/h3&gt;

&lt;p&gt;The overview sits at the top of the sidebar and shows the shop at a glance. Remember that several cards display placeholder figures until you connect real numbers, so keep a separate, trusted source for revenue until you do.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7s14b4mj6dcj0dv65xsx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7s14b4mj6dcj0dv65xsx.jpg" alt="Admin dashboard overview of the Next.js e-commerce template in the dark theme" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at the latest orders and the bell in the top bar, which is where new activity shows first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving orders along
&lt;/h3&gt;

&lt;p&gt;Filter the list by order status or payment status; the newest orders come first. Open an order, check the items, totals and addresses, add the tracking number when it ships, and move it forward one step. Cancel only before the order is closed. The payment status is set by the payment, never by hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiujqdy7yz2k8oalynrwu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiujqdy7yz2k8oalynrwu.jpg" alt="Orders with fulfilment progress in the dark theme, Next.js e-commerce admin dashboard" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fulfilment progress on each row shows how far an order has moved, so packers can scan for what is still waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Helping a customer
&lt;/h3&gt;

&lt;p&gt;The customers screen lists the shop's customers, and deleted ones have their own list. A customer who cannot track an order needs the exact order number and the email used at checkout, because tracking asks for both.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxzaq2penh3yxu1c456w.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flxzaq2penh3yxu1c456w.jpg" alt="Customer accounts screen in the Next.js e-commerce admin dashboard" width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use this list to find a customer by account and open their record when they write to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keeping the storefront fresh
&lt;/h3&gt;

&lt;p&gt;Rearrange the home page sections when you launch a new product, add an offer, or want a best seller pushed forward. Those edits are made in the admin, so they need no code change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings are stored values
&lt;/h3&gt;

&lt;p&gt;The App settings tab holds key and value pairs, including payment, email and general ones. The shipped storefront and API do not read them yet, so changing one changes nothing until your own code does.&lt;/p&gt;

&lt;h2&gt;
  
  
  An example launch plan by week
&lt;/h2&gt;

&lt;p&gt;This schedule is an example for Cedar &amp;amp; Cloth, not a promise about how long your launch will take.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 1, understand.&lt;/strong&gt; Run the stack, place a test order, and settle the decisions list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 2, brand and catalogue.&lt;/strong&gt; Rebrand both apps, rewrite the shipped copy, and add categories and one complete product.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 3, the rest of the shop.&lt;/strong&gt; Add the remaining products, arrange the home page, and set up bucket storage and photos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 4, money and staff.&lt;/strong&gt; Set the shipping rule, connect Stripe in test mode, create the roles for the packers, and run orders through every status.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 5, production.&lt;/strong&gt; Set up hosting, the environment files and the tables. Connect a mail provider, add tracking tags, and write the missing terms and privacy pages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Week 6, soft launch.&lt;/strong&gt; Start with a small audience, then switch to Stripe live keys when you are sure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Launch checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Store name, logos, icons, colors and fonts replaced in both apps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipped menswear copy rewritten in the brand file, the home messages and the SEO messages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contact details, social links and sharing images replaced.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Categories and products complete in both languages, with variants and photos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shipping rule changed in both files, if you changed it at all.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stripe live keys and the live webhook in place, after a full test-mode run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Roles ticked for each staff member, and the demo passwords changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;JWT_SECRET&lt;/code&gt;, &lt;code&gt;CORS_ORIGIN&lt;/code&gt;, &lt;code&gt;FRONTEND_URL&lt;/code&gt; and both frontend API addresses set for production.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;NEXT_PUBLIC_SITE_URL&lt;/code&gt; set to the real domain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A mail provider connected, and the contact form and newsletter form wired.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Terms and privacy pages written.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tracking ids pasted, and a consent banner added if you sell to the EU or UK.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Demo checkout prefill left off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Backups scheduled for the database and the media bucket.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Template, from scratch, or a hosted store builder
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A hosted store builder
&lt;/h3&gt;

&lt;p&gt;You can be selling quickly, with hosting, checkout and updates handled for you. You pay ongoing fees, work within the platform's design and rules, and keep your data on their system. It suits a shop that wants to sell, not to run software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building from scratch
&lt;/h3&gt;

&lt;p&gt;You get exactly what you design. You also build a catalogue with variants, carts, guest checkout, order tracking, payment and webhooks, accounts for customers and staff, permissions, an admin, two languages and an API before the first order. You maintain all of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting from a template
&lt;/h3&gt;

&lt;p&gt;You own the code, host the stack and change anything. The storefront, admin and API are already connected, so you spend your time on products and brand. In return you accept the work this guide lists: email, tax rules, terms pages, hosting and updates. It suits founders who want control, and agencies building stores for clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs to run
&lt;/h2&gt;

&lt;p&gt;Template packages start from $79, and the Full Stack package Cedar &amp;amp; Cloth uses is $179. It includes a commercial license and lifetime support. Beyond that you pay for what you switch on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Hosting for the storefront, admin and API, and a database if you move past SQLite.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stripe's processing fees, listed on &lt;a href="https://stripe.com/pricing" rel="noopener noreferrer"&gt;Stripe's pricing page&lt;/a&gt;. Cash on delivery needs no processor.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A bucket such as Cloudflare R2 for product images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Google Gemini key for the assistant, studio or fitting room, billed by Google, and a Higgsfield account for video.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A mail provider and a domain.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free parts: SQLite for local work, the demo store, cash on delivery, and background removal on your own server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to customise next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Copy and photos.&lt;/strong&gt; Replace the home page messages and the page titles in both languages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shipping rules.&lt;/strong&gt; Change the free-shipping threshold or fee in the two files together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Another tracking network.&lt;/strong&gt; The marketing tags are configured in &lt;code&gt;src/config/analytics.config.ts&lt;/code&gt;, and the template describes adding one more as a single-file job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Admin overview cards.&lt;/strong&gt; Wire the sample revenue and conversion cards to your own numbers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Roles.&lt;/strong&gt; Build a role for each person on the team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A third language.&lt;/strong&gt; Add it to both frontends and the API. Products hold two languages, English and Arabic.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Card orders that stay pending
&lt;/h3&gt;

&lt;p&gt;An order flips to paid only after Stripe's webhook reaches your API; the shopper coming back to the site does not do it. Check the endpoint address, the two subscribed events and the signing secret.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing the shipping rule in only one place
&lt;/h3&gt;

&lt;p&gt;The cart, checkout summary and API must agree. Edit the storefront config and the orders service in the same change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leaving the frontends on the sample store
&lt;/h3&gt;

&lt;p&gt;Without &lt;code&gt;NEXT_PUBLIC_API_BASE_URL&lt;/code&gt;, each frontend answers from sample data in the browser, shows a notice, and charges no one. Set the address and rebuild.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counting every GA4 session twice
&lt;/h3&gt;

&lt;p&gt;If GA4 loads from Tag Manager, leave the template's GA4 variable empty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handing out an empty role
&lt;/h3&gt;

&lt;p&gt;Admin, Manager and Editor start with no permissions. A staff member given one sees almost nothing until you tick what they need.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I build an online store with an admin panel in Next.js?
&lt;/h3&gt;

&lt;p&gt;Use a template that already has the storefront, the dashboard and the API as one project, and change the brand and catalogue. This one gives you all three, with a seeder that fills the database with a demo store in one command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use it for clients?
&lt;/h3&gt;

&lt;p&gt;Yes. The license covers unlimited projects, including stores built for a customer and handed over. You may not resell the code or release it as a template.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it support Arabic and RTL?
&lt;/h3&gt;

&lt;p&gt;Yes. The storefront, the admin and the API ship in English and Arabic, with Arabic laid out right to left. Products and categories are stored with both languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a backend?
&lt;/h3&gt;

&lt;p&gt;For real orders, yes. The frontends run alone on a sample store, but real orders, payments and AI features depend on the NestJS API, which comes with the Full Stack and Backend API packages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it support guest checkout?
&lt;/h3&gt;

&lt;p&gt;Yes. Guests check out with an email and track the order by number and email, with no account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which payment methods work?
&lt;/h3&gt;

&lt;p&gt;Card payments run on Stripe, and shoppers can also choose cash on delivery. PayPal is not offered at checkout.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which database does it use?
&lt;/h3&gt;

&lt;p&gt;SQLite for local work and MySQL by configuration, with no code change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it handle tax?
&lt;/h3&gt;

&lt;p&gt;No. The shipping rule and checkout total apply no tax. If your business must charge it, add that to the pricing logic on both the storefront and the API so the totals stay equal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can customers pay in other currencies?
&lt;/h3&gt;

&lt;p&gt;Card payments are charged in US dollars. Selling in another currency means changing that in code, and the tracking currency is a separate setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens when a card is declined?
&lt;/h3&gt;

&lt;p&gt;The order is marked failed when Stripe reports the failed payment. Cash on delivery remains available for that customer.&lt;/p&gt;

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

&lt;p&gt;Open the &lt;a href="https://ecommerce-clothes-1.aniq-ui.com/en" rel="noopener noreferrer"&gt;storefront demo&lt;/a&gt; and the &lt;a href="https://ecommerce-clothes-1-admin.aniq-ui.com/en/dashboard" rel="noopener noreferrer"&gt;admin demo&lt;/a&gt;. Then read the &lt;a href="https://www.aniq-ui.com/en/templates/next-js-ecommerce-template/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; or visit the &lt;a href="https://www.aniq-ui.com/en/templates/next-js-ecommerce-template" rel="noopener noreferrer"&gt;e-commerce template page&lt;/a&gt;. More choices are in the &lt;a href="https://www.aniq-ui.com/en/templates/category/ecommerce-nextjs-online-store-templates" rel="noopener noreferrer"&gt;e-commerce templates&lt;/a&gt; category and the &lt;a href="https://www.aniq-ui.com/en/blog/nextjs-ecommerce-templates" rel="noopener noreferrer"&gt;Next.js e-commerce templates guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The e-commerce template is also one of the ten templates in the &lt;a href="https://www.aniq-ui.com/en/templates/nextjs-templates-bundle" rel="noopener noreferrer"&gt;Next.js Templates Bundle&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ecommerce</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a full-stack Next.js 16 e-commerce monorepo: storefront, admin, NestJS API, and AI try-on</title>
      <dc:creator>Aniq UI</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:08:11 +0000</pubDate>
      <link>https://dev.to/aniqui/i-built-a-full-stack-nextjs-16-e-commerce-monorepo-storefront-admin-nestjs-api-and-ai-try-on-2kbb</link>
      <guid>https://dev.to/aniqui/i-built-a-full-stack-nextjs-16-e-commerce-monorepo-storefront-admin-nestjs-api-and-ai-try-on-2kbb</guid>
      <description>&lt;p&gt;I shipped a production e-commerce system as a single monorepo: a Next.js 16 storefront, a Next.js 16 admin dashboard, and a NestJS API. English and Arabic with full RTL, guest checkout, and three AI features that live inside the codebase instead of being bolted on top.&lt;/p&gt;

&lt;p&gt;This is the build log. The decisions I would defend, the ones that cost me a week, and what I would do differently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three apps, not one
&lt;/h2&gt;

&lt;p&gt;The obvious move is one Next.js app with &lt;code&gt;/admin&lt;/code&gt; routes behind a guard. I split it into three.&lt;/p&gt;

&lt;p&gt;The storefront and the admin have almost nothing in common at the boundary. The storefront is public, SEO-critical, and cached aggressively. The admin is authenticated on every request, data-heavy, and never indexed. Sharing a build means the storefront pays for Recharts, the data grids, and the admin's client bundle even when nobody signs in.&lt;/p&gt;

&lt;p&gt;Splitting also made auth honest. Customers and staff are different tables with different token payloads and different refresh lifetimes. When they share an app you inevitably end up with one &lt;code&gt;User&lt;/code&gt; model carrying an &lt;code&gt;isAdmin&lt;/code&gt; boolean, and RBAC on top of a boolean is how privilege escalation bugs happen.&lt;/p&gt;

&lt;p&gt;Cost of the split: three ports in dev, a shared types package, and CORS config you have to actually think about. Worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  RTL is a layout problem, not a translation problem
&lt;/h2&gt;

&lt;p&gt;Arabic support is where most templates quietly fail. Translating strings is the easy half. The hard half is that every hardcoded &lt;code&gt;margin-left&lt;/code&gt;, &lt;code&gt;left-0&lt;/code&gt;, &lt;code&gt;flex-row&lt;/code&gt;, and chevron icon is now wrong.&lt;/p&gt;

&lt;p&gt;The rule that fixed it: no physical direction properties anywhere. Logical properties only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* breaks in RTL */&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;padding-right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* works in both */&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="py"&gt;padding-inline-end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&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;Tailwind 4 makes this workable because &lt;code&gt;ms-*&lt;/code&gt;, &lt;code&gt;me-*&lt;/code&gt;, &lt;code&gt;ps-*&lt;/code&gt;, &lt;code&gt;pe-*&lt;/code&gt;, &lt;code&gt;start-*&lt;/code&gt;, and &lt;code&gt;end-*&lt;/code&gt; are first class. Once I banned &lt;code&gt;ml-&lt;/code&gt;, &lt;code&gt;mr-&lt;/code&gt;, &lt;code&gt;pl-&lt;/code&gt;, &lt;code&gt;pr-&lt;/code&gt;, &lt;code&gt;left-&lt;/code&gt;, and &lt;code&gt;right-&lt;/code&gt; from the codebase, the same components served both directions with no mirrored variants.&lt;/p&gt;

&lt;p&gt;The leftovers that still bite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Icons with implied direction. A "next" chevron has to flip. A logo does not. There is no automatic rule, you tag them by hand.&lt;/li&gt;
&lt;li&gt;Carousels. Embla needs its direction option set from the locale or drag goes the wrong way.&lt;/li&gt;
&lt;li&gt;Numbers and currency. &lt;code&gt;Intl.NumberFormat&lt;/code&gt; with the right locale, not string concatenation.&lt;/li&gt;
&lt;li&gt;Transforms. &lt;code&gt;translateX&lt;/code&gt; does not mirror. Animations needed direction-aware values.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For messages I used next-intl with one file per feature per locale rather than one giant &lt;code&gt;en.json&lt;/code&gt;. A 2000-line translation file is unreviewable and guarantees merge conflicts. &lt;code&gt;checkout.en.json&lt;/code&gt; next to &lt;code&gt;checkout.ar.json&lt;/code&gt; means a translator touches one file and you can diff it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guest checkout: the order is the identity
&lt;/h2&gt;

&lt;p&gt;Forcing account creation before purchase kills conversion, so guest checkout was non-negotiable. It changes the data model more than you would expect.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;order.userId&lt;/code&gt; is non-nullable, every guest order needs a phantom user, and now you have a users table full of ghosts that break your customer analytics. Instead the order owns its own contact and shipping snapshot, and &lt;code&gt;userId&lt;/code&gt; is nullable.&lt;/p&gt;

&lt;p&gt;Snapshot is the key word. The shipping address on an order is a copy, not a foreign key to an address row. When a customer edits their saved address six months later, historical orders must not silently change. Same for line items: product name, variant, and unit price get denormalized onto the order line at purchase time. Prices change. Invoices should not.&lt;/p&gt;

&lt;p&gt;Guest order tracking is then just order number plus the email used at checkout. No account, no magic link infrastructure, and the lookup is rate-limited so it is not an enumeration oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual try-on
&lt;/h2&gt;

&lt;p&gt;A shopper taps a hanger icon on any product page and sees the garment worn, either on a built-in model or on their own uploaded photo.&lt;/p&gt;

&lt;p&gt;The architecture that made it feel fast:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The upload goes straight to S3-compatible storage with a presigned URL. The API never proxies the image bytes.&lt;/li&gt;
&lt;li&gt;Generation is a job, not a request. The client gets a job id immediately and subscribes over Socket.io.&lt;/li&gt;
&lt;li&gt;Results are cached per garment plus model pair, so the same product on the built-in model is generated once, ever. That is most of the traffic.&lt;/li&gt;
&lt;li&gt;Per-account quotas, enforced server-side. Image generation has real unit cost, and an unmetered endpoint is a bill waiting to happen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The piece I underestimated: composition. Shoppers do not want one item, they want an outfit. Building up a look piece by piece meant persisting the try-on session state so the next garment composites onto the previous result instead of starting from a blank model.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI studio lives inside the product editor
&lt;/h2&gt;

&lt;p&gt;Merchants do not want a separate AI tool. They want the product form to fill itself.&lt;/p&gt;

&lt;p&gt;So the generation UI sits in the product editor: generate product imagery from a prompt, remove backgrounds, draft the listing copy from a single uploaded photo, and turn a still into a short product video with motion presets.&lt;/p&gt;

&lt;p&gt;The detail that changed usage more than the model choice: three one-tap prompt suggestions sitting directly above the composer. "Write the product details." "Clean white studio shot." "Photograph this on a model." Tap, then Send. A blank prompt box gets ignored. Three buttons get pressed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The admin assistant queries the database, not the internet
&lt;/h2&gt;

&lt;p&gt;The dashboard assistant is a streaming chat, but the interesting part is that it answers from store data through an MCP tool registry rather than from general knowledge.&lt;/p&gt;

&lt;p&gt;Each tool is a typed, scoped function: revenue for a period, orders awaiting fulfilment, top sellers, low stock. The model picks tools and composes the answer. It cannot free-form SQL, so it cannot leak or wreck anything the tool contract does not allow.&lt;/p&gt;

&lt;p&gt;Two things fell out of this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provider choice is trivial. Anthropic, Google, and OpenAI all speak tools, so the provider is config. Bring your own key.&lt;/li&gt;
&lt;li&gt;Answers are auditable. Every claim traces to a tool call with real numbers, not a plausible hallucinated figure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suggested questions on the empty state again, for the same reason as the studio prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time without a state mess
&lt;/h2&gt;

&lt;p&gt;New orders, status changes, and notifications push over Socket.io. The temptation is to write incoming events into a client store and treat that store as the source of truth. That path ends in two copies of your data that disagree.&lt;/p&gt;

&lt;p&gt;What worked: the socket event carries an identifier, not a payload. The handler invalidates the relevant TanStack Query key and the normal fetch path refills it. One source of truth, no reconciliation logic, and a page that recovers correctly after a reconnect.&lt;/p&gt;

&lt;p&gt;Redux Toolkit stays for genuinely client-owned state: cart, wishlist, UI preferences. Server state never enters it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring things that mattered most
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A one-command seeder.&lt;/strong&gt; Products, variants, images, customers, orders across every status. Nothing kills first-run enthusiasm like an empty database and a broken chart.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact pinned dependencies with committed lockfiles.&lt;/strong&gt; No carets. A template that installs a different tree next month is a support ticket generator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Branding in one config file per app.&lt;/strong&gt; Colors, logo, store name. If a rebrand means grepping for hex codes, the theming is fake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No flash of the wrong theme.&lt;/strong&gt; A blocking inline script reads the stored preference before paint. Tiny detail, immediately noticeable when missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;I would design the RTL rule on day one instead of retrofitting it. Auditing every physical property after the fact took a week of tedious work that a lint rule would have prevented from the start.&lt;/p&gt;

&lt;p&gt;I would also make the AI features quota-gated from the first commit rather than adding limits later. Retrofitting metering into an existing generation flow is more invasive than it sounds.&lt;/p&gt;

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

&lt;p&gt;The whole thing is available as a template if you would rather start from a working store than a blank &lt;code&gt;create-next-app&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.aniq-ui.com/en/templates/next-js-ecommerce-template" rel="noopener noreferrer"&gt;Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.aniq-ui.com/en/templates" rel="noopener noreferrer"&gt;Template details and pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stack: Next.js 16 App Router, React 19, TypeScript 5, Tailwind CSS 4, TanStack Query, Redux Toolkit, React Hook Form and Zod, next-intl, NestJS, TypeORM with MySQL or SQLite, Socket.io.&lt;/p&gt;

&lt;p&gt;Happy to go deeper on any part of this in the comments. The RTL and the try-on caching are the two I have the most notes on.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>typescript</category>
      <category>ai</category>
    </item>
    <item>
      <title>How I Built My Own Next.js Template Store (And What I Learned)</title>
      <dc:creator>Aniq UI</dc:creator>
      <pubDate>Fri, 10 Oct 2025 17:07:11 +0000</pubDate>
      <link>https://dev.to/aniqui/how-i-built-my-own-nextjs-template-store-and-what-i-learned-53la</link>
      <guid>https://dev.to/aniqui/how-i-built-my-own-nextjs-template-store-and-what-i-learned-53la</guid>
      <description>&lt;p&gt;A few months ago, I decided to build my own Next.js landing page template store : &lt;a href="https://www.aniq-ui.com/en" rel="noopener noreferrer"&gt;https://www.aniq-ui.com/en&lt;/a&gt;.&lt;br&gt;
I wanted a place to sell templates I designed and coded myself, but I also wanted it to reflect my taste in UI and performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I Chose Next.js ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve worked with React for years, and Next.js gave me the balance between speed, SEO, and developer control.&lt;br&gt;
Its app router and server components made it easy to scale templates cleanly without messy state handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Stack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Next.js for structure and SSR&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TailwindCSS for consistent design&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TypeScript for safety and scalability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Framer Motion for subtle motion design&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple local JSON for i18n, later upgrading to a CMS&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Designing for reusability is harder than it looks.&lt;/p&gt;

&lt;p&gt;A fast page is nice, but good UX sells.&lt;/p&gt;

&lt;p&gt;You don’t need a team, just focus, taste, and iteration.&lt;/p&gt;

&lt;p&gt;🌍 Check It Out&lt;/p&gt;

&lt;p&gt;You can see the result here: &lt;a href="https://www.aniq-ui.com/en/templates" rel="noopener noreferrer"&gt;https://www.aniq-ui.com/en/templates&lt;/a&gt;&lt;/p&gt;

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