Fix: Analog Not Working — Routes Not Loading, API Endpoints Failing, or Vite Build Errors
Quick Answer
How to fix Analog (Angular meta-framework) issues — file-based routing, API routes with Nitro, content collections, server-side rendering, markdown pages, and deployment.
The Problem
Analog routes return 404:
GET /about → 404 Not FoundOr API routes don’t respond:
GET /api/users → 502 Bad GatewayOr the Vite dev server crashes:
Error: [vite] Cannot find module '@analogjs/vite-plugin-angular'Why This Happens
Analog is the Angular meta-framework built on Vite and Nitro. It brings file-based routing, API routes, and SSR to Angular:
- Routes use file-based convention in
src/app/pages/— each.page.tsfile becomes a route. The.pagesuffix is required — regular.component.tsfiles aren’t auto-routed. - API routes go in
src/server/routes/— Analog uses Nitro (same as Nuxt) for server endpoints. API files needdefineEventHandlerexports. - Analog needs the Vite plugin —
@analogjs/platformmust be installed and configured invite.config.ts. Without it, Angular components don’t compile. - Content/markdown requires
@analogjs/content— for MDX/markdown pages, the content plugin must be explicitly installed and configured.
Fix 1: Project Setup
# Create new Analog project
npm create analog@latest my-app
cd my-app && npm install && npm run dev
# Or add to existing Angular project
ng add @analogjs/platform// vite.config.ts
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
export default defineConfig({
plugins: [
analog({
ssr: true,
static: false,
prerender: {
routes: ['/', '/about', '/blog'],
},
}),
],
});// src/app/app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideFileRouter } from '@analogjs/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideFileRouter(), // File-based routing
provideHttpClient(withFetch()),
provideClientHydration(),
],
};Fix 2: File-Based Routing
src/app/pages/
├── index.page.ts # /
├── about.page.ts # /about
├── (auth)/
│ ├── login.page.ts # /login
│ └── register.page.ts # /register
├── blog/
│ ├── index.page.ts # /blog
│ └── [slug].page.ts # /blog/:slug
├── dashboard/
│ ├── index.page.ts # /dashboard
│ └── settings.page.ts # /dashboard/settings
└── [...not-found].page.ts # Catch-all 404// src/app/pages/index.page.ts — home page
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
@Component({
standalone: true,
imports: [RouterLink],
template: `
<h1>Welcome to Analog</h1>
<nav>
<a routerLink="/about">About</a>
<a routerLink="/blog">Blog</a>
<a routerLink="/dashboard">Dashboard</a>
</nav>
`,
})
export default class HomePage {}
// MUST be default export// src/app/pages/blog/[slug].page.ts — dynamic route
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { AsyncPipe } from '@angular/common';
import { toSignal } from '@angular/core/rxjs-interop';
import { switchMap } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Component({
standalone: true,
imports: [AsyncPipe],
template: `
@if (post(); as p) {
<article>
<h1>{{ p.title }}</h1>
<p>{{ p.body }}</p>
</article>
} @else {
<p>Loading...</p>
}
`,
})
export default class BlogPostPage {
private route = inject(ActivatedRoute);
private http = inject(HttpClient);
post = toSignal(
this.route.paramMap.pipe(
switchMap(params => this.http.get<Post>(`/api/posts/${params.get('slug')}`))
)
);
}Fix 3: API Routes (Nitro)
src/server/routes/
├── v1/
│ ├── users.ts # /api/v1/users
│ ├── users/
│ │ └── [id].ts # /api/v1/users/:id
│ └── posts.ts # /api/v1/posts
└── health.ts # /api/health// src/server/routes/v1/users.ts
import { defineEventHandler, readBody, getQuery } from 'h3';
// GET /api/v1/users
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const limit = Number(query.limit) || 20;
const users = await db.query.users.findMany({ limit });
return users;
});
// src/server/routes/v1/users/[id].ts
// Multiple HTTP methods in one file
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
if (event.method === 'GET') {
const user = await db.query.users.findFirst({ where: eq(users.id, id!) });
if (!user) throw createError({ statusCode: 404, message: 'User not found' });
return user;
}
if (event.method === 'PATCH') {
const body = await readBody(event);
const [updated] = await db.update(users).set(body).where(eq(users.id, id!)).returning();
return updated;
}
if (event.method === 'DELETE') {
await db.delete(users).where(eq(users.id, id!));
return { deleted: true };
}
});
// Or use specific method files:
// src/server/routes/v1/users.get.ts
// src/server/routes/v1/users.post.tsFix 4: Layouts
// src/app/pages/dashboard.page.ts — layout route
// Files named the same as a directory create a layout
import { Component } from '@angular/core';
import { RouterOutlet, RouterLink } from '@angular/router';
@Component({
standalone: true,
imports: [RouterOutlet, RouterLink],
template: `
<div class="flex min-h-screen">
<aside class="w-64 bg-gray-900 text-white p-4">
<h2 class="text-xl font-bold mb-4">Dashboard</h2>
<nav>
<a routerLink="/dashboard" routerLinkActive="text-blue-400" [routerLinkActiveOptions]="{ exact: true }">
Overview
</a>
<a routerLink="/dashboard/settings" routerLinkActive="text-blue-400">
Settings
</a>
</nav>
</aside>
<main class="flex-1 p-8">
<router-outlet />
</main>
</div>
`,
})
export default class DashboardLayout {}
// src/app/pages/dashboard/index.page.ts — /dashboard
@Component({
standalone: true,
template: `<h1>Dashboard Overview</h1>`,
})
export default class DashboardIndexPage {}
// src/app/pages/dashboard/settings.page.ts — /dashboard/settings
@Component({
standalone: true,
template: `<h1>Settings</h1>`,
})
export default class DashboardSettingsPage {}Fix 5: Markdown/Content Pages
npm install @analogjs/content marked prismjs// vite.config.ts — enable content plugin
import { defineConfig } from 'vite';
import analog, { type PrerenderContentFile } from '@analogjs/platform';
export default defineConfig({
plugins: [
analog({
content: {
highlighter: 'prism',
},
prerender: {
routes: async () => {
const contentFiles = await import.meta.glob<PrerenderContentFile>(
'/src/content/blog/*.md'
);
return [
'/',
'/blog',
...Object.keys(contentFiles).map(file => {
const slug = file.replace('/src/content/blog/', '').replace('.md', '');
return `/blog/${slug}`;
}),
];
},
},
}),
],
});<!-- src/content/blog/getting-started.md -->
---
title: Getting Started with Analog
date: 2026-03-30
slug: getting-started
description: Learn how to build apps with Analog
---
# Getting Started
This is a markdown blog post rendered by Analog.
```typescript
console.log('Hello from Analog!');
```typescript
// src/app/pages/blog/[slug].page.ts — render markdown content
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { injectContent, MarkdownComponent } from '@analogjs/content';
import { AsyncPipe } from '@angular/common';
interface PostAttributes {
title: string;
date: string;
slug: string;
description: string;
}
@Component({
standalone: true,
imports: [MarkdownComponent, AsyncPipe],
template: `
@if (post$ | async; as post) {
<article>
<h1>{{ post.attributes.title }}</h1>
<time>{{ post.attributes.date }}</time>
<analog-markdown [content]="post.content" />
</article>
}
`,
})
export default class BlogPostPage {
post$ = injectContent<PostAttributes>({
customFilename: this.route.snapshot.paramMap.get('slug')!,
});
constructor(private route: ActivatedRoute) {}
}Fix 6: Deployment
// vite.config.ts — deployment presets
export default defineConfig({
plugins: [
analog({
// Vercel
nitro: { preset: 'vercel' },
// Cloudflare Pages
// nitro: { preset: 'cloudflare-pages' },
// Netlify
// nitro: { preset: 'netlify' },
// Node.js server
// nitro: { preset: 'node-server' },
// Static site generation
// static: true,
// prerender: { routes: ['/', '/about', '/blog'] },
}),
],
});# Build
npm run build
# Preview production build locally
npm run serve
# Deploy
# Vercel: connect repo, auto-detected
# Netlify: set build command to `npm run build`, publish dir to `dist/analog/public`Still Not Working?
Routes return 404 — page files must use the .page.ts extension and export a default component. about.component.ts won’t work — it must be about.page.ts. Also check the file is in src/app/pages/.
API routes don’t respond — server routes must be in src/server/routes/ and export defineEventHandler. The /api/ prefix is added automatically. Also check the dev server is running with SSR enabled.
Vite build fails — ensure @analogjs/platform is installed and configured in vite.config.ts. Angular components need the Vite plugin to compile — without it, Angular decorators and templates aren’t processed.
Content/markdown not rendering — install @analogjs/content and marked. The content files must be in src/content/. The MarkdownComponent must be imported in the page component.
For related Angular issues, see Fix: Angular SSR Not Working and Fix: Angular Signals Not Updating.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Angular SSR Not Working — Hydration Failing, Window Not Defined, or Build Errors
How to fix Angular Server-Side Rendering issues — @angular/ssr setup, hydration, platform detection, transfer state, route-level rendering, and deployment configuration.
Fix: Astro Actions Not Working — Form Submission Failing, Validation Errors Missing, or Return Type Wrong
How to fix Astro Actions issues — action definition, Zod validation, form handling, progressive enhancement, error handling, file uploads, and calling actions from client scripts.
Fix: SolidStart Not Working — Routes Not Rendering, Server Functions Failing, or Hydration Errors
How to fix SolidStart issues — file-based routing, server functions, createAsync data loading, middleware, sessions, and deployment configuration.
Fix: TanStack Start Not Working — Server Functions Failing, Routes Not Loading, or SSR Errors
How to fix TanStack Start issues — project setup, file-based routing, server functions with createServerFn, data loading, middleware, SSR hydration, and deployment configuration.