@upbinger/blog-angular
v1.3.1
Published
Angular module for embedding Upbinger blog content with visit analytics
Maintainers
Readme
@upbinger/blog-angular
Angular module for embedding Upbinger blog content into your Angular application.
Installation
npm install @upbinger/blog-angular
# or
yarn add @upbinger/blog-angularQuick Start (Standalone Components - Angular 16+)
// app.routes.ts
import { Routes } from '@angular/router';
import { getUpbingerBlogRoutes } from '@upbinger/blog-angular';
export const routes: Routes = [
{ path: '', component: HomeComponent },
// Add blog routes - handles /blog, /blog/:slug, /blog/page-:page
...getUpbingerBlogRoutes(),
];// app.config.ts
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
]
};Quick Start (NgModule)
// app.module.ts
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { UpbingerBlogModule, getUpbingerBlogRoutes } from '@upbinger/blog-angular';
@NgModule({
imports: [
UpbingerBlogModule.forRoot({
cdnBase: 'https://cdn.upbinger.com',
debug: false,
}),
RouterModule.forRoot([
{ path: '', component: HomeComponent },
...getUpbingerBlogRoutes(),
]),
],
})
export class AppModule {}Configuration
Configure the service in your app initialization:
// app.component.ts
import { Component, inject, OnInit } from '@angular/core';
import { UpbingerBlogService } from '@upbinger/blog-angular';
@Component({
selector: 'app-root',
template: `<router-outlet></router-outlet>`
})
export class AppComponent implements OnInit {
private blogService = inject(UpbingerBlogService);
ngOnInit() {
this.blogService.configure({
cdnBase: 'https://cdn.upbinger.com',
basePath: '/blog',
debug: true,
domain: 'example.com', // Optional: override domain detection
});
}
}Using Components Directly
For custom layouts, use the content component directly:
import { Component } from '@angular/core';
import { UpbingerBlogContentComponent } from '@upbinger/blog-angular';
@Component({
selector: 'app-custom-blog',
standalone: true,
imports: [UpbingerBlogContentComponent],
template: `
<div class="my-custom-wrapper">
<upbinger-blog-content
type="post"
[slug]="'my-post-slug'">
</upbinger-blog-content>
</div>
`
})
export class CustomBlogComponent {}Content Component Inputs
| Input | Type | Description |
|-------|------|-------------|
| type | 'listing' \| 'post' | Content type to load |
| slug | string | Blog post slug (for type='post') |
| page | number | Page number (for type='listing') |
Using the Service
For fully custom implementations:
import { Component, inject } from '@angular/core';
import { UpbingerBlogService } from '@upbinger/blog-angular';
@Component({
selector: 'app-custom',
template: `
<div *ngIf="loading">Loading...</div>
<div *ngIf="content" [innerHTML]="content"></div>
`
})
export class CustomComponent {
private blogService = inject(UpbingerBlogService);
loading = true;
content = '';
ngOnInit() {
this.blogService.fetchContent('my-post.html').subscribe({
next: (html) => {
this.content = this.blogService.extractBodyContent(html);
this.loading = false;
},
error: (err) => {
console.error(err);
this.loading = false;
}
});
}
}Routes Created
| Path | Description |
|------|-------------|
| /blog | Blog listing (page 1) |
| /blog/page-2 | Blog listing (page 2, etc.) |
| /blog/my-post-slug | Individual blog post |
SEO — read this
This package renders blog content into the light DOM (not Shadow DOM) with
the CDN stylesheet scoped to #upbinger-blog, so it is crawlable. But because
this package runs in the browser, it is important to understand exactly what
that does and does not cover.
✅ What this package covers: JavaScript-executing crawlers
The post body, <title>, canonical, Open Graph/Twitter tags and JSON-LD are
produced after your JavaScript runs, so only clients that execute JS see them:
- Googlebot (renders JS in a second pass) — posts get indexed with full body text.
- Headless/Chromium-based crawlers and most modern SEO tools that render JS.
❌ What this package does NOT cover: non-JS crawlers (first-byte HTML)
A client-rendered Angular SPA returns an empty <div id="root">/app-root shell
in the first HTTP response; the blog HTML only appears after the JS bundle runs.
Clients that do not execute JavaScript get nothing. Examples that will see a
blank page / no preview:
- Social link previews (no JS at all): Facebook (
facebookexternalhit), X/Twitter, LinkedIn, WhatsApp, Slack, Discord, Telegram, Pinterest — these read only the static HTML<head>/Open Graph tags, so a JS-injected preview shows blank. - AI / LLM crawlers (generally no JS): GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, Google-Extended, Amazonbot, Bytespider, CCBot.
- Any tool that reads only the raw HTML response.
Bing / DuckDuckBot — partial: Bingbot can render JavaScript (it's evergreen, Edge-based), but Bing itself recommends dynamic rendering because it can't render JS reliably at scale or across all frameworks — so JS-only content may be indexed late or missed. (DuckDuckGo largely uses Bing's index.)
This is not a bug — no browser-side code can put content in the first byte, because the crawler has already received the empty shell before any JS runs.
🔒 To cover non-JS crawlers too (full SEO): render on the server
Full, first-byte SEO requires a server in the request path to return the blog HTML before the first byte. Options:
- Angular SSR (
@angular/ssr) / Analog / any server build — fetch the CDN HTML on the server for/blog/*and return it, then let this package hydrate / take over client navigation. (Server adapter available — contact Upbinger.) - Plain client-only SPA (static
ng buildon static hosting) — there is no server to render on, so add a tiny edge / serverless function (Cloudflare Worker, Vercel/Netlify Edge, Firebase/Cloud Function, or Lambda) on your/blog/**route that does dynamic rendering: serve the prerendered CDN HTML to bots and your normal SPA to humans. Upbinger ships a ready-to-deploy version (dashboard → Publish → Integration → Full SEO). - Don't want any server piece — you keep the ✅ coverage (Googlebot indexes your posts); the ❌ non-JS clients stay uncovered. Valid if Google is your only target.
The CDN already hosts fully SEO-complete HTML for every post
(cdn.upbinger.com/blogs/<your-domain>/<slug>.html), so the server piece is only
a thin proxy.
Exported Items
Components (Standalone)
UpbingerBlogComponent- Main container with router outletUpbingerBlogContentComponent- Renders blog contentUpbingerBlogListingComponent- Listing page (page 1)UpbingerBlogListingPageComponent- Listing page with paginationUpbingerBlogPostComponent- Individual blog post
Services
UpbingerBlogService- Configuration and content fetching
Functions
getUpbingerBlogRoutes(basePath?: string)- Returns route configuration
Modules
UpbingerBlogModule- NgModule for non-standalone usage
Customizable Styles
Blog grid and post styles are fully configurable from the Upbinger dashboard — navigate to Publish → Style tab. Changes include:
| Setting | Affects | |---------|---------| | Hero Title & Subtitle | Grid listing page heading text | | Primary Color | Card hover borders, pagination, links | | Background / Text Colors | Page and card backgrounds, text | | Card Border Radius | Rounded corners on blog cards | | Grid Max Width / Padding | Listing layout dimensions | | Post Accent Color | Headings, links, blockquotes, keywords | | Post Max Width / Margin Top | Blog post layout | | Font Family | Blog post typography | | Back Button | Show/hide "← Back to Blog" link |
After saving styles, republish your blogs for changes to take effect.
Requirements
- Angular 16+
- HttpClientModule (or provideHttpClient())
- RouterModule (or provideRouter())
- Blogs published through Upbinger dashboard
License
MIT
