# How to Set Up LunaTV with Next.js 14 App Router: Complete Installation Guide

> Learn how to set up LunaTV with Next.js 14 App Router. Follow our complete installation guide to get the streaming app running quickly. Clone, install, configure, and run.

- Repository: [MoonTechLab/LunaTV](https://github.com/MoonTechLab/LunaTV)
- Tags: getting-started
- Published: 2026-09-08

---

**Clone the LunaTV repository, install dependencies with pnpm, configure your `.env.local` file with `NEXT_PUBLIC_*` variables, and run `pnpm dev` to start the Next.js 14 App Router on `localhost:3000`.**

LunaTV is a React-based media center built on Next.js 14 that leverages the App Router architecture for server-side rendering and dynamic routing. The application follows the modern `src/app` directory structure, utilizing React Server Components by default while injecting runtime configuration for client-side hydration. This guide walks you through the complete setup process using the official MoonTechLab/LunaTV source code.

## Clone the Repository and Install Dependencies

Start by cloning the repository and installing the required packages. LunaTV uses pnpm as its package manager, though npm and yarn are supported alternatives.

```bash
git clone https://github.com/MoonTechLab/LunaTV.git
cd LunaTV
pnpm install

```

The installation process sets up Next.js 14 along with the custom webpack configuration needed for SVG transformation and PWA support defined in [`next.config.js`](https://github.com/MoonTechLab/LunaTV/blob/main/next.config.js).

## Configure Environment Variables

Create a `.env.local` file in the project root to store sensitive configuration and public runtime variables. This file is automatically ignored by Git according to the repository's `.gitignore`, ensuring credentials remain secure.

```dotenv
USERNAME=admin
PASSWORD=your_secure_password
NEXT_PUBLIC_STORAGE_TYPE=kvrocks
KVROCKS_URL=redis://localhost:6666
NEXT_PUBLIC_SITE_NAME=LunaTV
ANNOUNCEMENT=Welcome to LunaTV!
NEXT_PUBLIC_DOUBAN_PROXY_TYPE=cmliussss-cdn-tencent

```

**Critical variables for Next.js 14 App Router setup:**
- `NEXT_PUBLIC_STORAGE_TYPE` – Defines the storage backend (kvrocks, redis, or upstash)
- `NEXT_PUBLIC_SITE_NAME` – Injected into the browser via `window.RUNTIME_CONFIG` for client components
- `USERNAME` and `PASSWORD` – Admin credentials used by the auth middleware

The configuration loader in [`src/lib/config.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/config.ts) reads these values at runtime, falling back to a JSON config file if environment variables are not present.

## Start the Development Server

Launch the application using the development script:

```bash
pnpm dev

```

The server initializes on **http://localhost:3000** with hot reloading enabled. Because [`next.config.js`](https://github.com/MoonTechLab/LunaTV/blob/main/next.config.js) specifies `output: 'standalone'`, the build process generates a self-contained Node.js server suitable for containerized deployments.

Verify the setup by navigating to the root URL. The home page renders via [`src/app/page.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/page.tsx) while the global layout in [`src/app/layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/layout.tsx) wraps the content with theme providers and site-wide metadata.

## Understanding the App Router Architecture

### Root Layout and Server Components

The [`src/app/layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/layout.tsx) file serves as the Root Layout for the entire application. As a React Server Component, it executes exclusively on the server, enabling direct access to environment variables and external data sources during the request.

According to the source code, this layout performs three critical functions:
1. Loads configuration via `await getConfig()` from [`src/lib/config.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/config.ts)
2. Generates dynamic metadata using the loaded site configuration
3. Injects a `<script>` tag that serializes `window.RUNTIME_CONFIG` (lines 85-94) for client-side consumption

```tsx
// src/app/layout.tsx (conceptual structure)
import { getConfig } from '@/lib/config';

export default async function RootLayout({ children }) {
  const cfg = await getConfig();
  return (
    <html lang="en">
      <body>
        <script
          dangerouslySetInnerHTML={{
            __html: `window.RUNTIME_CONFIG = ${JSON.stringify(cfg)}`
          }}
        />
        {children}
      </body>
    </html>
  );
}

```

### File-System Routing Conventions

LunaTV uses the Next.js 14 App Router convention where directories in `src/app` map directly to URL paths. Dynamic segments use bracket notation `[slug]`, and each route folder requires a [`page.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/page.tsx) file to expose the UI.

To create a new route, add a folder structure:

```

src/app/
├── page.tsx           # /

├── layout.tsx         # Global layout

└── about/
    └── page.tsx       # /about

```

Example implementation for a custom about page:

```tsx
// src/app/about/page.tsx
export default function AboutPage() {
  return (
    <main className="container mx-auto p-4">
      <h1 className="text-2xl font-bold">About LunaTV</h1>
      <p>Media management powered by Next.js 14 App Router.</p>
    </main>
  );
}

```

### Runtime Configuration Injection

Client components cannot access server-side environment variables directly. LunaTV solves this by serializing configuration into the HTML document head within [`layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/layout.tsx). Client components read from `window.RUNTIME_CONFIG` to access values like `NEXT_PUBLIC_STORAGE_TYPE` without bundling secrets into the JavaScript bundle.

## Accessing Configuration in Components

### Server Component Pattern

Use `getConfig()` in async server components to fetch settings during SSR:

```tsx
// src/app/dashboard/page.tsx
import { getConfig } from '@/lib/config';

export default async function Dashboard() {
  const cfg = await getConfig();
  const storageBackend = cfg.SiteConfig.StorageType;
  
  return (
    <div>
      <h1>Dashboard</h1>
      <p>Connected to: {storageBackend}</p>
    </div>
  );
}

```

This pattern avoids exposing sensitive configuration to the client while allowing the component to render with fresh data on each request.

### Client Component Pattern

For interactive components marked with the `'use client'` directive, access the injected global:

```tsx
// src/components/StorageIndicator.tsx
'use client';
import { useEffect, useState } from 'react';

export default function StorageIndicator() {
  const [type, setType] = useState<string>('unknown');

  useEffect(() => {
    // Access runtime config injected by layout.tsx
    const config = (window as any).RUNTIME_CONFIG;
    setType(config?.STORAGE_TYPE ?? 'localstorage');
  }, []);

  return <span>Storage: {type}</span>;
}

```

The `window.RUNTIME_CONFIG` object contains the serialized output of `getConfig()`, providing identical values to both server and client contexts.

## Dynamic Metadata Generation

Next.js 14 App Router supports async `generateMetadata` exports that execute during the server render. LunaTV implements this pattern in [`src/app/layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/layout.tsx) to set page titles and OpenGraph tags based on the site configuration.

Example for a dynamic blog route:

```tsx
// src/app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
import { getPostMeta } from '@/lib/blog';

export async function generateMetadata({ 
  params 
}: { 
  params: { slug: string } 
}): Promise<Metadata> {
  const meta = await getPostMeta(params.slug);
  return {
    title: `${meta.title} | LunaTV`,
    description: meta.excerpt,
  };
}

```

This executes at request time, allowing SEO metadata to reflect current database state rather than build-time constants.

## Deployment Configuration

### Docker Standalone Build

The repository includes a production-ready `Dockerfile` that leverages the standalone output mode:

```bash

# Build the standalone output

pnpm build

# Run the container

docker run -p 3000:3000 -e NEXT_PUBLIC_SITE_NAME=LunaTV lunatv:latest

```

The [`docker-compose.dev.yml`](https://github.com/MoonTechLab/LunaTV/blob/main/docker-compose.dev.yml) file provides a local development stack including Kvrocks and Redis for testing the data layer defined in [`src/lib/db.client.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/db.client.ts).

### Platform-Specific Deployment

**Vercel:** Connect the GitHub repository; the build command `next build` executes automatically using the App Router. Ensure environment variables are configured in the Vercel dashboard, not just `.env.local`.

**Zeabur:** The containerized build works out-of-the-box. Set the `NEXT_PUBLIC_*` variables in the Zeabur console before deploying.

**Self-hosted:** Use the standalone output to run Node.js directly without containerization, though Docker is recommended for production consistency.

## Summary

- **Repository structure:** LunaTV uses Next.js 14 with the App Router in `src/app/`, where folders define routes and [`layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/layout.tsx) provides global server-side wrapping.
- **Configuration flow:** [`src/lib/config.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/config.ts) provides `getConfig()` for server components, while [`src/app/layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/layout.tsx) injects values into `window.RUNTIME_CONFIG` for client components.
- **Environment setup:** Required variables include `NEXT_PUBLIC_STORAGE_TYPE`, database URLs, and admin credentials in `.env.local`.
- **Development workflow:** Run `pnpm dev` for local development; use `output: 'standalone'` in [`next.config.js`](https://github.com/MoonTechLab/LunaTV/blob/main/next.config.js) for production Docker builds.
- **Extensibility:** Add new routes by creating folders under `src/app`, and access shared configuration consistently across server and client boundaries.

## Frequently Asked Questions

### Does LunaTV require the Pages Router or App Router?

LunaTV requires the **App Router** introduced in Next.js 13 and stabilized in Next.js 14. The application relies on React Server Components in [`src/app/layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/layout.tsx) and [`src/app/page.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/page.tsx) for server-side data fetching and runtime configuration injection. The traditional Pages Router in `src/pages` is not used in this codebase.

### How does LunaTV handle environment variables in client components?

Client components cannot access `process.env` directly. Instead, the Root Layout in [`src/app/layout.tsx`](https://github.com/MoonTechLab/LunaTV/blob/main/src/app/layout.tsx) serializes the configuration object into a global `window.RUNTIME_CONFIG` variable using a `<script>` tag. Client components read from this object at runtime, ensuring sensitive values never appear in the client-side JavaScript bundle while still allowing public configuration to be accessible.

### What storage backends are supported by the data layer?

According to [`src/lib/db.client.ts`](https://github.com/MoonTechLab/LunaTV/blob/main/src/lib/db.client.ts), LunaTV abstracts storage behind a unified client that supports **Redis**, **Kvrocks**, and **Upstash**. Set `NEXT_PUBLIC_STORAGE_TYPE` to your preferred backend and provide the connection URL (e.g., `KVROCKS_URL`) in your environment file. The [`docker-compose.dev.yml`](https://github.com/MoonTechLab/LunaTV/blob/main/docker-compose.dev.yml) includes a Kvrocks service for local development.

### Can I customize the Next.js configuration for external image CDNs?

Yes. The [`next.config.js`](https://github.com/MoonTechLab/LunaTV/blob/main/next.config.js) file disables Next.js image optimization (`images: { unoptimized: true }`) specifically to support external CDN hosting without the Image Optimization API. You can also modify the webpack configuration (lines 38-55) to adjust SVG handling via `@svgr/webpack` or enable additional PWA features through the `withPWA` wrapper.