Peningkatan TypeScript Next.js 15.5: Typed Routes dan Type Safety Terlengkap

Revolusi TypeScript di Next.js 15.5

Next.js 15.5 menghadirkan pembaharuan yang sangat dinanti-nantikan para developer, terutama dalam hal integrasi TypeScript yang lebih mendalam. Kalau sebelumnya kita sering mengalami kesulitan dalam menangani routing dengan type safety yang optimal, sekarang Next.js memberikan solusi yang lebih elegan dan powerfull.

Salah satu breakthrough terbesar dalam versi ini adalah stabilnya fitur typed routes yang selama ini masih dalam tahap experimental. Fitur ini memungkinkan kita untuk memiliki intellisense yang sempurna ketika melakukan navigasi antar halaman. Bayangkan kalau kita punya aplikasi besar seperti platform pembelajaran BuildWithAngga, dimana ada ratusan route yang harus dikelola, fitur ini benar-benar game changer.

// Sebelum Next.js 15.5 - rawan typo dan error
const router = useRouter();
router.push('/kelas/frontend-developer/materi-1'); // bisa salah ketik

// Dengan Next.js 15.5 - fully typed
router.push('/kelas/[category]/[slug]', {
  category: 'frontend-developer',
  slug: 'materi-1'
}); // auto-complete dan type checking

Sistem validasi route export menjadi lebih ketat dan informatif. Next.js sekarang akan memberikan warning yang lebih spesifik ketika kita melakukan export yang tidak sesuai dengan standar. Misalnya kalau kita lupa export default component di page file, atau ketika structure folder tidak sesuai dengan app router convension.

// pages/kelas/[...slug].tsx
interface KelasPageProps {
  params: {
    slug: string[];
  };
  searchParams: { [key: string]: string | string[] | undefined };
}

export default function KelasPage({ params, searchParams }: KelasPageProps) {
  // TypeScript sekarang bisa validate structure params
  const [category, courseSlug, materialSlug] = params.slug;

  return (
    <div>
      <h1>Kelas {category}</h1>
      <h2>Course: {courseSlug}</h2>
      {materialSlug && <h3>Materi: {materialSlug}</h3>}
    </div>
  );
}

// Export yang diperlukan untuk proper type generation
export const generateStaticParams = async () => {
  // Implementation here
};

Peningkatan generasi type global membuat developer experience menjadi jauh lebih smooth. Next.js 15.5 secara otomatis generate types untuk semua route yang ada dalam aplikasi kita. Ini berarti ketika kita menggunakan Link component atau router.push(), TypeScript akan tahu persis route mana saja yang valid dalam aplikasi kita.

Type generation ini juga bekerja dengan dynamic routes dan catch-all routes. Jadi kalau kita punya struktur route yang kompleks seperti /kelas/[category]/[course]/[module]/[lesson], TypeScript akan generate interface yang tepat untuk setiap level parameter.

// Generated types (otomatis dibuat Next.js)
type ValidRoutes =
  | '/dashboard'
  | '/kelas'
  | '/kelas/[category]'
  | '/kelas/[category]/[course]'
  | '/mentor'
  | '/mentor/[mentorId]';

// Usage dengan full type safety
function NavigationMenu() {
  const menuItems: Array<{ label: string; href: ValidRoutes }> = [
    { label: 'Dashboard', href: '/dashboard' },
    { label: 'Kelas', href: '/kelas' },
    { label: 'Mentor', href: '/mentor' },
  ];

  return (
    <nav>
      {menuItems.map(item => (
        <Link key={item.href} href={item.href}>
          {item.label}
        </Link>
      ))}
    </nav>
  );
}

Yang paling menarik adalah integrasi dengan middleware TypeScript yang sekarang lebih robust. Kita bisa define custom type untuk middleware function yang akan di-enforce di seluruh aplikasi. Ini sangat berguna untuk aplikasi yang memiliki logic authentication dan authorization yang kompleks seperti platform BuildWithAngga dimana ada berbagai role user seperti student, mentor, dan admin.

Implementasi Typed Routes

image.png

Setup dan konfigurasi typed routes di Next.js 15.5 menjadi jauh lebih straightforward dibandingkan versi sebelumnya. Yang perlu kita lakukan adalah mengaktifkan feature flag typedRoutes di dalam file next.config.js. Prosesnya cukup sederhana, tapi impact yang dihasilkan sangat signifikan untuk development workflow kita.

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  experimental: {
    typedRoutes: true,
  },
  typescript: {
    // Memastikan type checking lebih strict
    ignoreBuildErrors: false,
  },
};

export default nextConfig;

Setelah konfigurasi ini diaktifkan, Next.js akan mulai generate file .next/types/link.d.ts yang berisi semua definisi route dalam aplikasi kita. File ini akan di-update secara otomatis setiap kali kita menambah atau mengubah structure routing. Ini berarti kita tidak perlu manual maintain type definitions untuk routes lagi.

Yang menarik adalah bagaimana typed routes berintegrasi dengan component Link dan useRouter hook. Sekarang ketika kita menggunakan Link component, TypeScript akan memberikan intellisense untuk semua valid routes dalam aplikasi. Misalnya dalam konteks BuildWithAngga, ketika kita mau navigate ke halaman kelas tertentu:

// components/KelasCard.tsx
import Link from 'next/link';
import { type Course } from '@/types/course';

interface KelasCardProps {
  course: Course;
}

export function KelasCard({ course }: KelasCardProps) {
  return (
    <div className="kelas-card">
      <h3>{course.title}</h3>
      <p>{course.description}</p>

      {/* TypeScript akan validate href ini */}
      <Link href={`/kelas/${course.category}/${course.slug}`}>
        Mulai Belajar
      </Link>

      {/* Bisa juga dengan object syntax untuk complex params */}
      <Link
        href={{
          pathname: '/kelas/[category]/[slug]',
          params: {
            category: course.category,
            slug: course.slug
          }
        }}
      >
        Detail Kelas
      </Link>
    </div>
  );
}

Type safety compile-time untuk routing adalah salah satu fitur yang paling powerfull. Berbeda dengan runtime validation yang baru ketahuan errornya ketika aplikasi dijalankan, compile-time validation akan langsung memberikan error di editor kita. Ini berarti typo atau kesalahan routing akan langsung ketahuan bahkan sebelum kita save file.

Ketika kita salah mengetik route atau menggunakan parameter yang tidak ada, TypeScript compiler akan memberikan error message yang jelas. Misalnya kalau kita punya route /kelas/[category]/[slug] tapi kita salah ketik jadi /kelas/[categorys]/[slug], maka akan langsung muncul error.

// app/dashboard/student/page.tsx
'use client';

import { useRouter } from 'next/navigation';

export default function StudentDashboard() {
  const router = useRouter();

  const handleNavigateToKelas = (courseId: string) => {
    // Error: Route tidak valid
    // router.push('/kelas/frontend/materi'); // typo di parameter

    // Correct: TypeScript akan validate ini
    router.push(`/kelas/frontend-developer/${courseId}`);
  };

  return (
    <div>
      <h1>Dashboard Student</h1>
      <button onClick={() => handleNavigateToKelas('react-fundamental')}>
        Ke React Fundamental
      </button>
    </div>
  );
}

Integrasi dengan Turbopack membuat proses development menjadi lebih cepat dan efisien. Turbopack sebagai bundler baru dari Next.js team, dioptimalkan khusus untuk bekerja dengan typed routes. Ketika kita menggunakan next dev --turbopack, proses regeneration types untuk routes menjadi hampir instan.

Turbopack juga memberikan hot reload yang lebih smart untuk typed routes. Kalau sebelumnya kita harus restart development server ketika menambah route baru, sekarang Turbopack akan otomatis detect perubahan routing dan update types secara real-time. Ini sangat membantu ketika kita sedang develop fitur yang melibatkan banyak navigasi antar halaman.

// package.json
{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "type-check": "tsc --noEmit"
  }
}

Yang lebih impressive lagi, Turbopack bisa detect circular dependencies dalam routing yang mungkin terjadi ketika kita punya struktur route yang kompleks. Ini preventive measure yang sangat berguna untuk maintainability aplikasi jangka panjang, apalagi kalau kita develop aplikasi sekala besar seperti platform pembelajaran BuildWithAngga.

Fitur Type Lanjutan

Setelah kita memahami implementasi dasar typed routes, saatnya kita menggali lebih dalam tentang fitur-fitur type yang lebih canggih di Next.js 15.5. Fitur-fitur ini akan memberikan pengalaman developer yang jauh lebih powerful dan aman ketika membangun aplikasi kompleks seperti platform BuildWithAngga.

PageProps dan LayoutProps merupakan dua interface yang sangat penting dalam ekosistem typed routes Next.js 15.5. PageProps memberikan type definition yang sangat ketat untuk props yang diterima oleh page component, sementara LayoutProps melakukan hal yang sama untuk layout component. Kedua interface ini secara otomatis di-generate berdasarkan struktur routing yang kita buat, jadi kita tidak perlu manual mendefinisikan type untuk setiap page atau layout.

// app/kelas/[category]/[slug]/page.tsx
interface PageProps {
  params: Promise<{
    category: string;
    slug: string;
  }>;
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}

export default async function KelasDetailPage({
  params,
  searchParams,
}: PageProps) {
  // TypeScript akan validate bahwa params.category dan params.slug ada
  const { category, slug } = await params;
  const { module, lesson } = await searchParams;

  // Contoh penggunaan searchParams dengan type safety
  const moduleId = module as string | undefined;
  const lessonId = lesson as string | undefined;

  return (
    <div className="container mx-auto px-4 py-8">
      <h1>
        Kelas {category}: {slug}
      </h1>
      {moduleId && <p>Module: {moduleId}</p>}
      {lessonId && <p>Lesson: {lessonId}</p>}
    </div>
  );
}

// app/kelas/layout.tsx
interface LayoutProps {
  children: React.ReactNode;
  params: {
    category?: string;
    slug?: string;
  };
}

export default function KelasLayout({ children, params }: LayoutProps) {
  return (
    <div className="min-h-screen bg-gray-50">
      <header className="bg-white shadow-sm">
        <div className="container mx-auto px-4 py-4">
          <nav className="flex items-center space-x-4">
            <Link href="/kelas">Semua Kelas</Link>
            {params.category && (
              <Link href={`/kelas/${params.category}`}>
                {params.category.replace('-', ' ')}
              </Link>
            )}
            {params.slug && (
              <span className="text-gray-500">{params.slug}</span>
            )}
          </nav>
        </div>
      </header>
      <main>{children}</main>
    </div>
  );
}

RouteContext types memberikan konteks yang lebih luas tentang route saat ini, termasuk informasi tentang parent routes dan nested routing structure. Ini sangat berguna ketika kita membangun aplikasi dengan struktur routing yang kompleks seperti platform pembelajaran BuildWithAngga dimana ada hierarki kelas, modul, dan materi.

// types/route-context.ts
export interface RouteContext {
  currentPath: string;
  params: Record<string, string>;
  searchParams: Record<string, string | string[]>;
  parentRoute?: string;
  childRoutes?: string[];
}
// hooks/useRouteContext.ts
'use client';

import { usePathname, useParams, useSearchParams } from 'next/navigation';
import { RouteContext } from '@/types/route-context';

export function useRouteContext(): RouteContext {
  const pathname = usePathname();
  const params = useParams();
  const searchParams = useSearchParams();

  // Convert searchParams to object
  const searchParamsObject: Record<string, string | string[]> = {};
  searchParams.forEach((value, key) => {
    const existing = searchParamsObject[key];
    if (existing) {
      searchParamsObject[key] = Array.isArray(existing)
        ? [...existing, value]
        : [existing, value];
    } else {
      searchParamsObject[key] = value;
    }
  });

  // Determine parent route
  const pathSegments = pathname.split('/').filter(Boolean);
  const parentRoute = pathSegments.length > 1
    ? '/' + pathSegments.slice(0, -1).join('/')
    : undefined;

  return {
    currentPath: pathname,
    params: params as Record<string, string>,
    searchParams: searchParamsObject,
    parentRoute
  };
}

Validasi route export menjadi lebih sophisticated di Next.js 15.5. Framework sekarang tidak hanya memvalidasi apakah kita export default component dengan benar, tapi juga memastikan bahwa semua required exports seperti generateStaticParams, generateMetadata, dan dynamic exports sudah sesuai dengan standar dan type-safe.

// app/kelas/[category]/[slug]/page.tsx
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { courses } from '@/data/courses';

// Type-safe metadata generation
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
  const course = courses.find(c =>
    c.category === params.category && c.slug === params.slug
  );

  if (!course) {
    return {
      title: 'Kelas Tidak Ditemukan | BuildWithAngga'
    };
  }

  return {
    title: `${course.title} | BuildWithAngga`,
    description: course.description,
    openGraph: {
      title: course.title,
      description: course.description,
      images: [course.thumbnail]
    }
  };
}

// Type-safe static params generation
export async function generateStaticParams() {
  // Next.js akan validate return type ini
  return courses.map(course => ({
    category: course.category,
    slug: course.slug,
  }));
}

// Dynamic route configuration dengan type safety
export const dynamic = 'force-static';
export const revalidate = 3600; // 1 hour

export default function KelasDetailPage({ params, searchParams }: PageProps) {
  const course = courses.find(c =>
    c.category === params.category && c.slug === params.slug
  );

  if (!course) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-4">{course.title}</h1>
      <p className="text-gray-600 mb-6">{course.description}</p>
    </div>
  );
}

Generasi file type guard merupakan salah satu fitur paling menarik di Next.js 15.5. Framework akan otomatis membuat type guard functions untuk memvalidasi route parameters dan searchParams secara runtime. Ini berarti kita bisa memiliki type safety yang benar-benar end-to-end, dari compile time hingga runtime.

// .next/types/route-guards.d.ts (auto-generated)
declare module '@/types/route-guards' {
  export function isValidKelasParams(
    params: any
  ): params is { category: string; slug: string };

  export function isValidMentorParams(
    params: any
  ): params is { mentorSlug: string };

  export function validateSearchParams(
    searchParams: any
  ): { [key: string]: string | string[] | undefined };
}

// utils/route-validation.ts
export function validateKelasRoute(params: unknown):
  { category: string; slug: string } | null {

  if (typeof params !== 'object' || !params) return null;

  const { category, slug } = params as any;

  // Validate category
  const validCategories = ['frontend-developer', 'backend-developer', 'ui-ux-design'];
  if (!validCategories.includes(category)) return null;

  // Validate slug format
  if (typeof slug !== 'string' || !/^[a-z0-9-]+$/.test(slug)) return null;

  return { category, slug };
}
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Validate kelas routes
  if (pathname.startsWith('/kelas/')) {
    const segments = pathname.split('/').filter(Boolean);

    if (segments.length >= 3) {
      const category = segments[1];
      const slug = segments[2];

      const validationResult = validateKelasRoute({ category, slug });
      if (!validationResult) {
        return NextResponse.redirect(new URL('/kelas', request.url));
      }
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/kelas/:path*', '/mentor/:path*']
};

Yang paling impressive adalah bagaimana Next.js 15.5 mengintegrasikan semua fitur type lanjutan ini dengan development tools. Ketika kita menggunakan VS Code atau editor TypeScript lainnya, kita akan mendapat intellisense yang sangat detail untuk semua props, parameter, dan exports. Error messages juga menjadi lebih informatif dan memberikan suggestion yang actionable untuk memperbaiki type errors.

Peningkatan Developer Experience

Developer experience di Next.js 15.5 mengalami transformasi yang luar biasa berkat implementasi typed routes yang lebih matang. Sebagai developer yang sudah bertahun-tahun bekerja dengan berbagai framework, saya bisa bilang bahwa peningkatan ini benar-benar game changing, terutama untuk project besar seperti platform BuildWithAngga dimana ada ratusan route yang harus dikelola.

Auto-completion dan IntelliSense sekarang bekerja dengan tingkat akurasi yang hampir sempurna. Ketika kita mengetik Link component atau useRouter hook, TypeScript langsung memberikan suggestion untuk semua valid routes yang ada dalam aplikasi kita. Yang lebih menakjubkan lagi, IntelliSense tidak hanya menampilkan route path, tapi juga menampilkan informasi tentang required parameters dan optional query parameters.

// components/NavigationMenu.tsx
'use client';

import Link from 'next/link';
import { useRouter } from 'next/navigation';

function QuickNavigation() {
  const router = useRouter();

  const handleKelasNavigation = (category: string) => {
    // Ketika mengetik ini, IntelliSense akan suggest:
    // /kelas/frontend-developer
    // /kelas/backend-developer
    // /kelas/ui-ux-design
    router.push(`/kelas/${category}`);
  };

  return (
    <div className="quick-nav">
      {/* Auto-completion akan suggest semua valid routes */}
      <Link href="/kelas/frontend-developer">
        Frontend Developer
      </Link>

      {/* Parameter suggestions juga muncul */}
      <Link href="/mentor/angga-risky">
        Angga Risky Profile
      </Link>

      <button onClick={() => handleKelasNavigation('backend-developer')}>
        Backend Classes
      </button>
    </div>
  );
}

export default QuickNavigation;

image.png

IntelliSense juga memberikan informasi kontekstual yang sangat berguna. Misalnya, ketika kita hover di atas route tertentu, akan muncul tooltip yang menampilkan informasi tentang page component yang akan di-render, required props, dan bahkan preview dari metadata yang akan di-generate. Ini sangat membantu ketika kita bekerja dalam tim dan ingin memahami struktur aplikasi dengan cepat.

// app/dashboard/components/ClassCard.tsx
'use client';

import Link from 'next/link';
import { Course } from '@/types/course';

interface ClassCardProps {
  course: Course;
  studentProgress?: number;
}

export function ClassCard({ course, studentProgress = 0 }: ClassCardProps) {
  // IntelliSense akan menampilkan preview:
  // - Route: /kelas/[category]/[slug]
  // - Required params: category, slug
  // - Component: KelasDetailPage
  // - Metadata: Generated from course data

  return (
    <div className="class-card border rounded-lg p-6">
      <h3 className="text-xl font-semibold mb-2">{course.title}</h3>
      <p className="text-gray-600 mb-4">{course.description}</p>

      {/* Hover untuk melihat route information */}
      <Link
        href={`/kelas/${course.category}/${course.slug}`}
        className="bg-blue-500 text-white px-4 py-2 rounded"
      >
        Lanjutkan Belajar
      </Link>

      {studentProgress > 0 && (
        <div className="mt-4">
          <div className="text-sm text-gray-500">Progress: {studentProgress}%</div>
          <div className="w-full bg-gray-200 rounded-full h-2">
            <div
              className="bg-green-500 h-2 rounded-full transition-all"
              style={{ width: `${studentProgress}%` }}
            />
          </div>
        </div>
      )}
    </div>
  );
}

image.png

Deteksi error saat compile time menjadi jauh lebih canggih dan informatif. Next.js 15.5 tidak hanya memberitahu kita bahwa ada error, tapi juga memberikan konteks yang jelas tentang apa yang salah dan bagaimana cara memperbaikinya. Error messages sekarang dilengkapi dengan suggestion dan bahkan auto-fix untuk kasus-kasus tertentu.

// app/kelas/[category]/components/EnrollButton.tsx
'use client';

import { useRouter } from 'next/navigation';

interface EnrollButtonProps {
  courseSlug: string;
  category: string;
  isEnrolled?: boolean;
}

export function EnrollButton({ courseSlug, category, isEnrolled = false }: EnrollButtonProps) {
  const router = useRouter();

  const handleEnroll = () => {
    if (isEnrolled) {
      // TypeScript error jika route tidak valid:
      // Error: Route '/kelas/frontend/react-basics' is not valid
      // Suggestion: Did you mean '/kelas/frontend-developer/react-basics'?
      // router.push(`/kelas/frontend/${courseSlug}`); // WRONG

      // Correct version dengan auto-completion
      router.push(`/kelas/${category}/${courseSlug}`);
    } else {
      // Navigate to enrollment page
      router.push(`/enrollment?course=${courseSlug}&category=${category}`);
    }
  };

  return (
    <button
      onClick={handleEnroll}
      className={`px-6 py-2 rounded-lg font-medium ${
        isEnrolled
          ? 'bg-green-500 text-white'
          : 'bg-blue-500 text-white hover:bg-blue-600'
      }`}
    >
      {isEnrolled ? 'Lanjutkan Belajar' : 'Mulai Kelas'}
    </button>
  );
}

Yang sangat membantu adalah error detection untuk dynamic routes. Kalau sebelumnya kita sering kebingungan dengan parameter yang missing atau salah format, sekarang TypeScript akan langsung memberitahu kita dengan pesan error yang spesifik. Error message bahkan dilengkapi dengan example yang benar.

// utils/courseNavigation.ts
import { courses } from '@/data/courses';

export function generateCourseUrl(courseId: string) {
  const course = courses.find(c => c.id === courseId);

  if (!course) {
    throw new Error(`Course with id ${courseId} not found`);
  }

  // TypeScript akan validate format parameter
  // Error jika category atau slug mengandung karakter invalid
  return `/kelas/${course.category}/${course.slug}`;
}

export function navigateToLesson(
  category: string,
  courseSlug: string,
  lessonSlug: string
) {
  // Compile-time error jika route structure salah:
  // Expected: /kelas/[category]/[slug]/lesson/[lessonSlug]
  // Actual: /kelas/[category]/[slug]/[lessonSlug]
  // return `/kelas/${category}/${courseSlug}/${lessonSlug}`; // WRONG

  // Correct structure
  return `/kelas/${category}/${courseSlug}/lesson/${lessonSlug}`;
}

// Type-safe parameter validation
export function validateCourseParams(params: {
  category?: string;
  slug?: string;
}): { isValid: boolean; errors: string[] } {
  const errors: string[] = [];

  const validCategories = ['frontend-developer', 'backend-developer', 'ui-ux-design'];

  if (!params.category) {
    errors.push('Category parameter is required');
  } else if (!validCategories.includes(params.category)) {
    errors.push(`Invalid category. Expected one of: ${validCategories.join(', ')}`);
  }

  if (!params.slug) {
    errors.push('Slug parameter is required');
  } else if (!/^[a-z0-9-]+$/.test(params.slug)) {
    errors.push('Slug must contain only lowercase letters, numbers, and hyphens');
  }

  return {
    isValid: errors.length === 0,
    errors
  };
}

Tips integrasi IDE merupakan bagian penting untuk memaksimalkan developer experience. Untuk VS Code, ada beberapa extension dan konfigurasi yang sangat direkomendasikan untuk bekerja dengan typed routes Next.js 15.5. Extension seperti TypeScript Importer dan Auto Rename Tag menjadi sangat powerful ketika dikombinasikan dengan typed routes.

// .vscode/settings.json
{
  "typescript.preferences.importModuleSpecifier": "relative",
  "typescript.suggest.autoImports": true,
  "typescript.updateImportsOnFileMove.enabled": "always",
  "editor.codeActionsOnSave": {
    "source.organizeImports": true,
    "source.fixAll": true
  },
  "files.associations": {
    "*.tsx": "typescriptreact"
  },
  "emmet.includeLanguages": {
    "typescript": "html",
    "typescriptreact": "html"
  }
}

// .vscode/extensions.json
{
  "recommendations": [
    "bradlc.vscode-tailwindcss",
    "ms-vscode.vscode-typescript-next",
    "formulahendry.auto-rename-tag",
    "christian-kohler.path-intellisense",
    "ms-vscode.vscode-json"
  ]
}

Untuk memaksimalkan IntelliSense, kita juga bisa membuat custom type definitions yang akan membantu VS Code memberikan suggestion yang lebih akurat. Type definitions ini bisa kita letakkan di file terpisah dan import sesuai kebutuhan.

// types/navigation.ts
export type ValidRoutes =
  | '/'
  | '/kelas'
  | `/kelas/${CourseCategory}`
  | `/kelas/${CourseCategory}/${string}`
  | '/mentor'
  | `/mentor/${string}`
  | '/dashboard'
  | '/dashboard/student';

export type CourseCategory =
  | 'frontend-developer'
  | 'backend-developer'
  | 'ui-ux-design';

export interface NavigationItem {
  label: string;
  href: ValidRoutes;
  icon?: string;
  children?: NavigationItem[];
}

// Custom hook dengan type safety
export function useTypedNavigation() {
  const router = useRouter();

  const navigateTo = (route: ValidRoutes) => {
    router.push(route);
  };

  const navigateToKelas = (category: CourseCategory, slug?: string) => {
    if (slug) {
      navigateTo(`/kelas/${category}/${slug}`);
    } else {
      navigateTo(`/kelas/${category}`);
    }
  };

  return { navigateTo, navigateToKelas };
}

Yang paling impressive adalah integrasi dengan debugging tools. Ketika terjadi error routing, stack trace yang ditampilkan sekarang jauh lebih informatif dan dilengkapi dengan informasi tentang expected vs actual route structure. Ini sangat membantu ketika kita troubleshoot masalah routing yang kompleks dalam aplikasi BuildWithAngga.

Praktek Terbaik dan Migrasi

Melakukan migrasi dari Next.js versi lama ke 15.5 dengan typed routes memang butuh perencanaan yang matang, terutama untuk aplikasi besar seperti BuildWithAngga yang sudah memiliki ratusan route dan komponen. Berdasarkan pengalaman saya melakukan migrasi beberapa project, ada beberapa strategi yang terbukti efektif untuk meminimalisir downtime dan memastikan transisi yang smooth.

Konversi proyek yang sudah ada sebaiknya dilakukan secara bertahap, bukan langsung big bang migration. Strategi yang saya rekomendasikan adalah memulai dengan mengaktifkan typed routes secara experimental terlebih dahulu, lalu secara perlahan mengkonversi route demi route. Pendekatan ini memungkinkan kita untuk mengidentifikasi dan memperbaiki masalah tanpa mengganggu fungsionalitas yang sudah ada.

// next.config.ts - Tahap 1: Aktivasi experimental
import { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    typedRoutes: true,
  },
  typescript: {
    // Toleransi error di tahap awal migrasi
    ignoreBuildErrors: false,
  },
  // Aktifkan untuk debugging
  logging: {
    fetches: {
      fullUrl: true,
    },
  },
}

export default nextConfig;

// utils/migration-helpers.ts
// Helper untuk migrasi bertahap
export function createLegacyRouteWrapper<T extends Record<string, any>>(
  legacyComponent: React.ComponentType<T>
) {
  return function WrappedComponent(props: T) {
    // Log untuk tracking migrasi progress
    if (process.env.NODE_ENV === 'development') {
      console.warn('Legacy route component detected:', legacyComponent.name);
    }

    return <legacyComponent {...props} />;
  };
}

// Type guard untuk validasi parameter lama
export function isValidLegacyParams(params: any): params is {
  category?: string;
  slug?: string;
  id?: string;
} {
  return params && typeof params === 'object';
}

Saat melakukan konversi, prioritaskan route yang paling sering diakses terlebih dahulu. Untuk platform BuildWithAngga, ini berarti dimulai dari homepage, halaman kelas utama, dan authentication routes. Route yang jarang diakses bisa dikerjakan di fase berikutnya.

// app/kelas/[category]/page.tsx - Contoh konversi tahap 1
import { courses } from '@/data/courses';
import { notFound } from 'next/navigation';
import { createLegacyRouteWrapper } from '@/utils/migration-helpers';

// Legacy component yang sudah ada
function LegacyKelasCategory({ params }: any) {
  // Implementation lama
  return <div>Legacy Category Page</div>;
}

// New typed component
interface KelasPageProps {
  params: {
    category: string;
  };
  searchParams: { [key: string]: string | string[] | undefined };
}

function NewKelasCategory({ params, searchParams }: KelasPageProps) {
  const { category } = params;
  const filteredCourses = courses.filter(course => course.category === category);

  if (filteredCourses.length === 0) {
    notFound();
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold mb-8">
        Kelas {category.replace('-', ' ')}
      </h1>
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        {filteredCourses.map(course => (
          <div key={course.id} className="border rounded-lg p-6">
            <h3 className="text-xl font-medium mb-2">{course.title}</h3>
            <p className="text-gray-600 mb-4">{course.description}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

// Feature flag untuk switching
const USE_NEW_TYPED_ROUTES = process.env.NEXT_PUBLIC_USE_TYPED_ROUTES === 'true';

export default function KelasPage(props: KelasPageProps) {
  if (USE_NEW_TYPED_ROUTES) {
    return <NewKelasCategory {...props} />;
  }

  // Fallback ke legacy component
  const WrappedLegacy = createLegacyRouteWrapper(LegacyKelasCategory);
  return <WrappedLegacy {...props} />;
}

export async function generateStaticParams() {
  return [
    { category: 'frontend-developer' },
    { category: 'backend-developer' },
    { category: 'ui-ux-design' },
  ];
}

Strategi type safety harus diimplementasikan secara konsisten di seluruh aplikasi. Saya merekomendasikan untuk membuat type definitions yang centralized dan reusable, sehingga semua komponen menggunakan interface yang sama dan tidak ada duplikasi definisi type.

// types/navigation.ts - Central type definitions
export type CourseCategory = 'frontend-developer' | 'backend-developer' | 'ui-ux-design';

export interface BasePageProps<T = {}> {
  params: T;
  searchParams: { [key: string]: string | string[] | undefined };
}

export interface CoursePageProps extends BasePageProps<{
  category: CourseCategory;
  slug: string;
}> {}

export interface MentorPageProps extends BasePageProps<{
  mentorSlug: string;
}> {}

// Navigation types yang konsisten
export interface NavigationRoute {
  path: string;
  params?: Record<string, string>;
  searchParams?: Record<string, string>;
}

export interface TypedLink {
  href: string;
  params?: Record<string, string>;
  isActive?: boolean;
}

// hooks/useTypeSafeNavigation.ts
'use client';

import { useRouter, usePathname } from 'next/navigation';
import { NavigationRoute, CourseCategory } from '@/types/navigation';

export function useTypeSafeNavigation() {
  const router = useRouter();
  const pathname = usePathname();

  const navigateToRoute = (route: NavigationRoute) => {
    let url = route.path;

    // Replace parameters in path
    if (route.params) {
      Object.entries(route.params).forEach(([key, value]) => {
        url = url.replace(`[${key}]`, value);
      });
    }

    // Add search parameters
    if (route.searchParams) {
      const searchParams = new URLSearchParams(route.searchParams);
      url += `?${searchParams.toString()}`;
    }

    router.push(url);
  };

  const navigateToKelas = (category: CourseCategory, slug?: string) => {
    if (slug) {
      navigateToRoute({
        path: '/kelas/[category]/[slug]',
        params: { category, slug }
      });
    } else {
      navigateToRoute({
        path: '/kelas/[category]',
        params: { category }
      });
    }
  };

  const navigateToMentor = (mentorSlug: string) => {
    navigateToRoute({
      path: '/mentor/[mentorSlug]',
      params: { mentorSlug }
    });
  };

  const isCurrentRoute = (path: string): boolean => {
    return pathname === path;
  };

  return {
    navigateToRoute,
    navigateToKelas,
    navigateToMentor,
    isCurrentRoute,
    currentPath: pathname
  };
}

Untuk memastikan type safety yang konsisten, implementasikan validation layer di setiap route handler. Ini akan membantu menangkap error yang mungkin terlewat saat compile time dan memberikan fallback yang graceful.

// utils/route-validators.ts
import { CourseCategory } from '@/types/navigation';
import { courses } from '@/data/courses';

export function validateCourseCategory(category: string): category is CourseCategory {
  const validCategories: CourseCategory[] = ['frontend-developer', 'backend-developer', 'ui-ux-design'];
  return validCategories.includes(category as CourseCategory);
}

export function validateCourseSlug(category: CourseCategory, slug: string): boolean {
  return courses.some(course =>
    course.category === category && course.slug === slug
  );
}

export function sanitizeParams(params: Record<string, string>): Record<string, string> {
  const sanitized: Record<string, string> = {};

  Object.entries(params).forEach(([key, value]) => {
    // Remove special characters and normalize
    sanitized[key] = value
      .toLowerCase()
      .replace(/[^a-z0-9-]/g, '')
      .replace(/--+/g, '-')
      .replace(/^-|-$/g, '');
  });

  return sanitized;
}

// Middleware untuk route validation
export function withRouteValidation<T extends Record<string, string>>(
  validator: (params: T) => boolean,
  fallbackRoute: string = '/'
) {
  return function routeValidator(params: T) {
    if (!validator(params)) {
      throw new Error(`Invalid route parameters. Redirecting to ${fallbackRoute}`);
    }
    return params;
  };
}

Pertimbangan performa menjadi sangat penting ketika mengimplementasikan typed routes di aplikasi yang sudah ada. Type checking yang lebih ketat memang memberikan safety yang lebih baik, tapi bisa berdampak pada build time dan development experience jika tidak dioptimalkan dengan baik.

// next.config.ts - Optimasi performa
import { NextConfig } from 'next';

const nextConfig: NextConfig = {
  experimental: {
    typedRoutes: true,
    // Optimasi untuk build performance
    turbo: {
      rules: {
        '*.tsx': ['tsx', 'typescript'],
        '*.ts': ['typescript']
      }
    }
  },

  typescript: {
    // Skip type checking di production build untuk speed
    ignoreBuildErrors: process.env.NODE_ENV === 'production',
  },

  // Optimasi bundle
  compiler: {
    removeConsole: process.env.NODE_ENV === 'production',
  },

  // Caching strategy
  onDemandEntries: {
    maxInactiveAge: 25 * 1000,
    pagesBufferLength: 2,
  }
}

export default nextConfig;

// tsconfig.json - Konfigurasi optimal untuk performa
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "es6"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "baseUrl": ".",
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

Untuk monitoring performa setelah migrasi, implementasikan metrics collection yang akan membantu kita memahami dampak typed routes terhadap performa aplikasi BuildWithAngga secara real-time.

// utils/performance-monitoring.ts
export function measureTypeCheckingTime() {
  if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
    const start = performance.now();

    return function endMeasurement(operationName: string) {
      const end = performance.now();
      console.log(`[TypeScript] ${operationName} took ${(end - start).toFixed(2)}ms`);
    };
  }

  return function() {}; // No-op in production
}

// Custom hook untuk performance monitoring
export function useRoutePerformance() {
  const measureNavigation = (routePath: string) => {
    const startTime = performance.now();

    return {
      complete: () => {
        const duration = performance.now() - startTime;

        // Send to analytics in production
        if (process.env.NODE_ENV === 'production') {
          // analytics.track('route_navigation', {
          //   route: routePath,
          //   duration: duration
          // });
        } else {
          console.log(`Navigation to ${routePath}: ${duration.toFixed(2)}ms`);
        }
      }
    };
  };

  return { measureNavigation };
}

Terakhir, pastikan untuk membuat documentation yang comprehensive tentang type definitions dan routing conventions yang digunakan dalam aplikasi. Ini akan sangat membantu tim developer lain yang bekerja dengan codebase yang sudah menggunakan typed routes.

Penutup

Next.js 15.5 dengan fitur typed routes benar-benar membawa revolusi dalam cara kita develop aplikasi web modern. Fitur-fitur seperti auto-generated types, compile-time validation, dan IntelliSense yang powerful memberikan pengalaman development yang jauh lebih aman dan produktif. Implementasi yang kita bahas dalam artikel ini, mulai dari setup dasar hingga praktek terbaik migrasi, dapat langsung kamu terapkan di project nyata.

Type safety yang comprehensive ini bukan hanya tentang menghindari bug, tapi juga tentang meningkatkan maintainability dan collaboration dalam tim. Ketika semua route sudah typed dengan baik, developer baru bisa lebih mudah memahami struktur aplikasi dan berkontribusi dengan confidence yang lebih tinggi. Pengalaman saya menunjukkan bahwa investasi waktu untuk setup typed routes di awal project akan terbayar berkali-kali lipat dalam jangka panjang.

Kalau kamu tertarik untuk mendalami lebih lanjut tentang Next.js dan TypeScript, saya sangat merekomendasikan untuk mengikuti kelas-kelas di BuildWithAngga. Platform ini menyediakan pembelajaran yang terstruktur dan praktis, mulai dari basic frontend development hingga advanced topics seperti server-side rendering dan performance optimization. Mentor-mentornya juga berpengalaman di industri, jadi kamu bisa belajar dari real-world experience yang valuable.

Belajar teknologi baru memang tidak selalu mudah, apalagi ketika harus mengikuti perkembangan yang cepat seperti di dunia React dan Next.js. Tapi ingat bahwa setiap master pernah menjadi beginer. Yang terpenting adalah konsistensi dan praktik langsung. Jangan takut untuk eksperimen dengan fitur-fitur baru, karena dengan mencoba-coba kita bisa memahami konsep dengan lebih mendalam. Typed routes mungkin terlihat kompleks di awal, tapi setelah kamu terbiasa, kamu akan merasakan betapa powerful-nya fitur ini untuk development sehari-hari. Keep learning, keep coding, dan jangan lupa untuk selalu update dengan teknologi terbaru!