Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

benpsnyder
Copy link
Contributor

@benpsnyder benpsnyder commented Jul 30, 2025

Summary

This PR introduces an experimental route tree generator for AnalogJS that provides type-safe routing with automatic code generation, inspired by TanStack Router's approach. The feature generates TypeScript interfaces for all routes, enables JSON-LD structured data injection for SEO, and provides comprehensive debugging tools for troubleshooting SSR conflicts.

Motivation & Context

AnalogJS's file-based routing is powerful but lacks compile-time type safety for route parameters and navigation. Developers often face runtime errors due to typos in route paths or missing required parameters. This feature brings the excellent developer experience of TanStack Router's type-safe routing to the Angular ecosystem through AnalogJS.

Additionally, modern web applications require structured data for SEO optimization. This PR also introduces automatic JSON-LD injection during SSR, making it easy to add schema.org structured data to any route.

Changes

Core Features

  1. Route Tree Plugin (packages/vite-plugin-angular/src/lib/route-tree-plugin.ts)

    • Scans src/app/pages directory for .page.ts files
    • Generates routeTree.gen.ts with type-safe route interfaces
    • Supports dynamic parameters, catch-all routes, and nested paths
    • Handles route metadata exports (routeMeta)
    • Provides hot reload support with automatic regeneration
  2. JSON-LD SSR Plugin (packages/vite-plugin-angular/src/lib/json-ld-ssr-plugin.ts)

    • Automatically injects JSON-LD structured data during SSR
    • Supports all schema.org types via schema-dts package
    • Integrates with route tree for automatic route-to-JSON-LD mapping
    • Handles complex SSR timing and module loading challenges
  3. Angular Vite Plugin Integration

    • Added experimental routeTree configuration option
    • Comprehensive debug flags for troubleshooting SSR conflicts
    • Configurable output paths and code formatting options

Configuration Options

experimental: {
  routeTree: {
    // Core options
    pagesDirectory: 'src/app/pages',
    generatedRouteTree: 'src/app/routeTree.gen.ts',
    quoteStyle: 'single' | 'double',
    semicolons: boolean,
    
    // Advanced features
    lazyLoading: boolean,
    angularRoutes: boolean,
    
    // Debug flags
    debugDisableRouteTreeGeneration: boolean,
    debugDisableJsonLdSSR: boolean,
    debugVerbose: boolean
  }
}

Generated Type Definitions

The plugin generates comprehensive type definitions including:

  • AnalogRoutesByPath - Map routes to component types
  • AnalogRoutesById - Access routes by ID
  • RouteParams<T> - Extract typed parameters for any route
  • Angular Router compatible route configurations
  • JSON-LD data map for SSR injection

Example Usage

// Type-safe navigation
import { AnalogRoute, RouteParams } from './app/routeTree.gen';

function navigateToRoute<T extends AnalogRoute>(
  route: T,
  params: RouteParams<T>
) {
  router.navigate([route], { queryParams: params });
}

// Usage
navigateToRoute('/products/:id', { id: '123' }); // ✅ Type-safe
navigateToRoute('/products/:id', {}); // ❌ Error - missing 'id'

Test Apps Updated

  • apps/analog-app: Added example pages with JSON-LD exports
    • article.page.ts - Article schema example
    • product.page.ts - Product schema example
    • event.page.ts - Event schema example
    • test-meta.page.ts - Route metadata example

File Changes Summary

  • New Files: 8 core files + 4 example pages
  • Modified Files: 82 files total
  • Lines Added: ~3,500
  • Key Packages Modified:
    • @analogjs/vite-plugin-angular
    • @analogjs/platform
    • @analogjs/router

Breaking Changes

None. This is an experimental opt-in feature that requires explicit configuration to enable.

Testing

  • Manual testing with the analog-app demo
  • E2E tests updated to handle route tree generation
  • Extensive debugging tools added for troubleshooting SSR conflicts
  • Tested with various route patterns and edge cases

Documentation

This PR includes comprehensive inline documentation and JSDoc comments. Additional documentation should be added to the AnalogJS docs site covering:

  • Getting started with route tree generation
  • Type-safe navigation patterns
  • JSON-LD structured data setup
  • Troubleshooting SSR conflicts
  • Migration guide from manual route definitions

Known Issues & Future Work

  1. SSR Middleware Conflicts: The JSON-LD SSR plugin can conflict with existing middleware during dev server startup. Debug flags are provided to isolate issues.

  2. Future Enhancements:

    • Incremental generation for better performance
    • Support for route guards and middleware types
    • VS Code extension for route autocomplete
    • Build-time route validation
    • Pre-compiled JSON-LD for production builds

Checklist

  • Code follows the project's style guidelines
  • Self-review completed
  • Comments added for complex code sections
  • No breaking changes introduced
  • Appropriate debug logging added
  • Documentation updates required
  • All tests passing

Related Issues

This addresses the need for type-safe routing in AnalogJS applications and brings feature parity with other modern meta-frameworks like TanStack Router and Next.js.


JSON-LD Structured Data

You can export JSON-LD structured data from your page files using schema-dts for type-safe SEO optimization:

// src/app/pages/article.page.ts
import { Component } from '@angular/core';
import type { WithContext, Article } from 'schema-dts';

export const routeJsonLd: WithContext<Article> = {
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: 'Your Article Title',
  description: 'Article description for search engines',
  author: {
    '@type': 'Person',
    name: 'Author Name',
  },
  datePublished: '2024-01-30',
  publisher: {
    '@type': 'Organization',
    name: 'Your Site Name',
    logo: {
      '@type': 'ImageObject',
      url: 'https://yoursite.com/logo.png',
    },
  },
};

@Component({
  selector: 'app-article',
  standalone: true,
  template: `<article>Your content</article>`,
})
export default class ArticlePageComponent {}

@netlify
Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for analog-ng-app ready!

Name Link
🔨 Latest commit ad7a0b0
🔍 Latest deploy log https://app.netlify.com/projects/analog-ng-app/deploys/6889e34f9c09ac000806affc
😎 Deploy Preview https://deploy-preview-1813--analog-ng-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit ad7a0b0
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/6889e34f6ae90000088fe7c7
😎 Deploy Preview https://deploy-preview-1813--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit ad7a0b0
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/6889e34f34374500084b7d92
😎 Deploy Preview https://deploy-preview-1813--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Jul 30, 2025

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit acdf257
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/68a17dc3ac31bb00086d7399
😎 Deploy Preview https://deploy-preview-1813--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@benpsnyder
Copy link
Contributor Author

@brandonroberts this is an attempt to scratch my own itch. If you like it I hope we could get it in as experimental so it can be iterated. I have an open PR on our internal repo for doing SEO optimization with SSR and I'd love to get something like this in to use. Thx <3

@benpsnyder
Copy link
Contributor Author

Will be restructuring this PR so functionality is isolated to its proper boundary

@brandonroberts
Copy link
Member

I like the idea of type-safe routes. This implementation will make all routes eagerly loaded and needs some more thought around the router with navigation and links. Let's close this PR and open a discussion on the proposal.

@benpsnyder
Copy link
Contributor Author

TanStack Router Style Route Tree: Implemented Fixes

I've addressed concerns about eager loading, plugin integration, and navigation utilities. Below is a detailed summary of the completed changes, with a Before/After Table showing how we resolved each issue. The implementation now aligns with Angular best practices, preserves performance, and enhances developer experience.

Issue Before After
Eager Loading Eager imports in routeTree.gen.ts (e.g., import IndexComponent from './pages/(home).page') loaded all components upfront, increasing bundle size and breaking code splitting. Replaced with loadComponent: () => import('./pages/(home).page').then(m => m.default). Generated Angular Routes array with lazy loading, preserving code splitting and optimizing performance.
Missing Plugin Integration routeTreePlugin imported but not added to Vite plugin array, leaving it inactive. Added conditional activation: (pluginOptions.experimental?.routeTree && routeTreePlugin({ ... })). Supports boolean/object configs, fully integrated with Vite workflows.
Incomplete Navigation Route tree provided types but no runtime navigation, Angular Router integration, or link components. Implemented navigateToRoute<T>(), isCurrentRoute<T>(), and AnalogLinkDirective. Generated Routes with loadComponent and routeMeta, ensuring type-safe routing.

Implemented Solutions

  1. Lazy Loading: Used dynamic imports to generate Angular Routes:

    const routes: Routes = [
      { path: '', loadComponent: () => import('./pages/(home).page').then(m => m.default) },
      { path: 'article', loadComponent: () => import('./pages/article.page').then(m => m.default) },
    ];
  2. Plugin Integration: Added to Vite plugin array:

    return [
      angularPlugin(),
      (pluginOptions.experimental?.routeTree && routeTreePlugin({ workspaceRoot: pluginOptions.workspaceRoot, ... })) as Plugin,
      routerPlugin(),
      pendingTasksPlugin(),
    ].filter(Boolean) as Plugin[];
  3. Navigation Utilities: Added type-safe helpers and directive:

    export function navigateToRoute<T extends AnalogRoute>(
      router: Router,
      route: T,
      params?: RouteParams<T>
    ): Promise<boolean> {
      return router.navigate([route], { queryParams: params });
    }
    @Directive({ selector: '[analogLink]', standalone: true })
    export class AnalogLinkDirective<T extends AnalogRoute> {
      @Input() analogLink!: T;
      @Input() analogParams?: RouteParams<T>;
      constructor(private router: Router) {}
      @HostListener('click', ['$event'])
      onClick(event: Event): void {
        event.preventDefault();
        navigateToRoute(this.router, this.analogLink, this.analogParams);
      }
    }
  4. Configurability: Made opt-in via vite.config.ts:

    experimental: {
      routeTree: { lazyLoading: true, angularRoutes: true, navigationHelpers: true, routerIntegration: 'lazy-routes' }
    }

Results

  • ✅ Lazy Loading: No upfront component loading; full code splitting preserved.
  • ✅ Plugin: Active in Vite workflows with flexible configuration.
  • ✅ Navigation: Type-safe utilities and directives integrated with Angular Router.
  • ✅ Build: Processed 18 routes, generated routeTree.gen.ts and Routes array.

Benefits

  • Performance: No bundle size increase, preserves Angular optimizations.
  • Compatibility: Aligns with Angular Router and lazy loading patterns.
  • Developer Experience: Compile-time validation, IDE autocomplete, no runtime errors.
  • Maintainability: Auto-managed code, excluded from linting/formatting.

@benpsnyder benpsnyder force-pushed the feat/tanstack-router-style-routeTree branch from b58a529 to aa428b5 Compare August 16, 2025 22:13
@benpsnyder
Copy link
Contributor Author

MUST CLOSE AFTER: #1839 refactor(platform): migrate to moduleResolution bundler for enhanced performance

The only way to get the middleware working with SSR was to consolidate to ESM and remove CommonJS

@benpsnyder benpsnyder force-pushed the feat/tanstack-router-style-routeTree branch from c13cb38 to e432b31 Compare August 17, 2025 03:51
@benpsnyder
Copy link
Contributor Author

I've rewritten the Nitro middleware handling system with robustness. Here's what the solution provides:

🎯 Key Improvements

1. Comprehensive Error Handling

  • Module Loading: Graceful fallback if module loading fails
  • Validation: Multi-level validation with detailed error messages
  • Execution: Safe wrapper that prevents crashes
  • Cache Management: Compatible with both Vite 4 and 5+ APIs

2. Intelligent Validation

  • H3 Handler Detection: Checks for __is_handler__ property
  • Function Signature: Validates parameter count (≤1 for H3 handlers)
  • Type Safety: Full TypeScript compliance with proper typing

3. Fault-Proof Design

  • Never Crashes: All errors are caught and logged, requests continue
  • Graceful Degradation: If middleware fails, request proceeds normally
  • Debug Information: Comprehensive logging for troubleshooting
  • Module Cache: Smart cache invalidation with fallback

4. Production-Ready Features

  • Memory Leak Prevention: Proper module graph management
  • Performance: Minimal overhead with lazy validation
  • Compatibility: Works across Vite versions and Node environments
  • Monitoring: Clear logging for operations teams

🚀 How It Works

// 1. Safe module loading with cache invalidation
const module = await viteServer.ssrLoadModule(filePath);

// 2. Multi-level validation
if (!handler || typeof handler !== 'function') {
  /* warn & continue */
}
if (!handler.__is_handler__ && handler.length > 1) {
  /* warn & continue */
}

// 3. Safe execution wrapper
try {
  const result = await handler(createEvent(req, res));
  if (!result) next(); // Continue chain
} catch (error) {
  console.error(error);
  next(); // Never fail the request
}

Benefits

  1. Zero Downtime: Middleware errors won't crash the server
  2. Self-Healing: Bad middleware is bypassed automatically
  3. Developer Friendly: Clear error messages for debugging
  4. Enterprise Ready: Comprehensive logging and monitoring
  5. Future Proof: Compatible with Vite API changes

The solution transforms middleware from a potential point of failure into a resilient, self-monitoring system that enhances rather than risks your application's stability! 🛡️

@benpsnyder
Copy link
Contributor Author

benpsnyder commented Aug 17, 2025

I modified the json-ld-ssr-plugin.ts file to:

  1. Defer route tree loading: Instead of trying to load the route tree early in the process, I moved the loading to happen only when actually needed (when HTML content is being processed).

  2. Better error handling: Added proper error handling around the require() call to prevent it from disrupting the SSR process.

  3. Automatic plugin disabling: If the route tree doesn't exist or the JSON-LD map is empty, the plugin now disables itself to avoid unnecessary processing.

  4. Safer module loading: Removed cache clearing that was potentially interfering with other module requires.

Key Changes Made

  • Modified packages/vite-plugin-angular/src/lib/json-ld-ssr-plugin.ts
  • Added lazy loading of the route tree only when needed
  • Improved error handling to prevent SSR disruption
  • Added automatic plugin disabling when no JSON-LD data is available

The redirect middleware (apps/analog-app/src/server/middleware/redirect.ts) was correctly implemented with a proper default export - the issue was with the JSON-LD plugin's module loading behavior.

@benpsnyder
Copy link
Contributor Author

Eureka! That was way too hard.
image

@benpsnyder
Copy link
Contributor Author

benpsnyder commented Aug 17, 2025

I like the idea of type-safe routes. This implementation will make all routes eagerly loaded and needs some more thought around the router with navigation and links. Let's close this PR and open a discussion on the proposal.

@brandonroberts I updated the original description of this PR. I left other helpful comments and also I feel I addressed your concern about eagerly loaded routes. I also added comprehensive JSDOC documentation with lessons-learned and pitfalls (discovered over 30 hours doing this), and added a nice debugging feature.

I really need this functionality for the https://snyder.tech website 😅😅😅 and, well, this is an experimental feature behind a flag, and targeted for the alpha branch. People (myself included) understand that experimental breaking changes won't be supported in migrations and this implementation could change quite a bit. At least for now ... progress over perfection!

Barring any major issues, could we get this into alpha and take up discussions while people can actually use it?

@benpsnyder
Copy link
Contributor Author

I was hoping to get more feedback on this PR before closing but for now, I am going to get it merged into the fork. If there is any refactoring to do, or significant change to functionality, we'll address it in a follow-up.

This PR is being closed and accelerated into this fork: ESM Transition Plan for AnalogJS v2.0.0-alpha #1844 | 📢⚠️ Fork Alert (for battle testing)

@benpsnyder benpsnyder closed this Aug 19, 2025
@benpsnyder
Copy link
Contributor Author

I was hoping to get more feedback on this PR before closing but for now, I am going to get it merged into the fork. If there is any refactoring to do, or significant change to functionality, we'll address it in a follow-up.

This PR is being closed and accelerated into this fork: ESM Transition Plan for AnalogJS v2.0.0-alpha #1844 | 📢⚠️ Fork Alert (for battle testing)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants