Skip to main content

Create a Blog with Scully

Scully makes it incredibly easy to add a blog to your Angular application. This guide walks you through the entire process from setup to publishing your first post.

Prerequisites

  • Angular application set up and running
  • Scully installed (ng add @scullyio/init)
  • Node.js and npm installed

Adding Blog Support

1

Generate Blog Module

Run the Scully blog schematic to set up everything automatically:
ng generate @scullyio/init:blog
This command will:
  • Create a blog module with routing
  • Set up the blog component
  • Create a ./blog folder for markdown files
  • Update your Scully configuration
  • Generate a sample blog post
You should see output like:
✅️ Update scully.your-app.config.js
UPDATE scully.your-app.config.js (653 bytes)
UPDATE src/app/app-routing.module.ts (726 bytes)
CREATE src/app/blog/blog-routing.module.ts (429 bytes)
CREATE src/app/blog/blog.component.css (157 bytes)
CREATE src/app/blog/blog.component.html (160 bytes)
CREATE src/app/blog/blog.component.spec.ts (639 bytes)
CREATE src/app/blog/blog.component.ts (508 bytes)
CREATE src/app/blog/blog.module.ts (391 bytes)
✅️ Blog ./blog/2020-03-24-blog.md file created
CREATE blog/2020-03-24-blog.md (95 bytes)
2

Custom Folder Name (Optional)

If you want to use a different folder name or customize settings:
ng generate @scullyio/init:markdown
You’ll be prompted for:
? What name do you want to use for the module? blog
? What slug do you want for the markdown file? title
? Where do you want to store your markdown files? mdblog
? Under which route do you want your files to be requested? blog
Or use flags to skip prompts:
ng generate @scullyio/init:markdown \
  --name="blog" \
  --slug="title" \
  --source-dir="mdblog" \
  --route="blog"
Available Options:
OptionDescriptionDefault
nameModule name'blog'
slugURL parameter name'id'
routingScopeRoot or Child routingChild
sourceDirFolder for markdown filesValue from name
routeRoute path before :slugValue from name
3

Review Generated Configuration

Open scully.<your-app>.config.ts to see the new route configuration:
import { ScullyConfig } from '@scullyio/scully';

export const config: ScullyConfig = {
  projectRoot: './src',
  projectName: 'my-app',
  outDir: './dist/static',
  routes: {
    '/blog/:slug': {
      type: 'contentFolder',
      slug: {
        folder: './blog',
      },
    },
  },
};

Creating Blog Posts

Generate a New Post

1

Use the Post Schematic

Create a new blog post with the Angular CLI:
ng generate @scullyio/init:post --name="My First Post"
You’ll be prompted for the target folder:
? What's the target folder for this post? blog
✅️ Blog ./blog/my-first-post.md file created
CREATE blog/my-first-post.md (99 bytes)
2

Edit the Generated File

Open blog/my-first-post.md:
---
title: My First Post
description: blog description
published: false
---

# My First Post

Add your content here!
The frontmatter contains:
  • title: Post title
  • description: Post description
  • published: Whether post is live (true/false)
3

Add Content

Write your blog post using markdown. Here’s a complete example:
---
title: Getting Started with Angular and Scully
description: Learn how to build blazing fast Angular applications
published: false
author: Jane Doe
date: 2024-03-15
tags: [angular, scully, jamstack]
---

# Getting Started with Angular and Scully

Scully is the best static site generator for Angular applications.

## Why Scully?

- Fast: Pre-rendered pages load instantly
- SEO Friendly: Search engines can easily crawl your content
- Simple: Works with your existing Angular app

## Conclusion

Start building amazing Angular sites with Scully today!
You can include code examples, images, and all standard Markdown syntax in your posts.

Post Options

The @scullyio/init:post schematic supports these options:
OptionDescriptionDefault
namePost title/filename'blog-X'
targetTarget directory'blog'
metaDataFileYAML template for frontmatterundefined
Example with options:
ng generate @scullyio/init:post \
  --name="Advanced Angular Techniques" \
  --target="blog" \
  --metaDataFile="./templates/post-template.yaml"

Building and Previewing

1

Build Your Angular App

ng build
2

Run Scully

Generate static pages:
npx scully
Scully will:
  • Discover your routes
  • Process markdown files
  • Generate static HTML
  • Create unpublished slugs for draft posts
Your markdown file will be updated with an unpublished slug:
---
title: My First Post
description: blog description
published: false
slugs:
  - ___UNPUBLISHED___kao8mvda_pmldPr7aN7owPpStZiuDXFZ1ILfpcv5Z
---
3

Serve the Static Site

npx scully serve
This starts two servers:
  • Angular app: http://localhost:1864/
  • Static site: http://localhost:1668/
Access your unpublished post at:
http://localhost:1668/blog/___UNPUBLISHED___kao8mvda_pmldPr7aN7owPpStZiuDXFZ1ILfpcv5Z

Publishing Posts

1

Update Frontmatter

Change published to true and remove the slugs array:
---
title: My First Post
description: blog description
published: true
---
2

Rebuild with Scully

npx scully
Scully creates a route based on the filename:
dist/static/blog/my-first-post/index.html
3

View Published Post

npx scully serve
Navigate to: http://localhost:1668/blog/my-first-post

Custom URL Slugs

Override the default filename-based slug:
---
title: My First Post
description: blog description
published: true
slug: awesome-angular-tutorial
---
The post will be available at: /blog/awesome-angular-tutorial

Displaying Blog Posts

List All Posts

Use Scully’s ScullyRoutesService to display blog posts:
src/app/home/home.component.ts
import { Component } from '@angular/core';
import { ScullyRoutesService, ScullyRoute } from '@scullyio/ng-lib';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
})
export class HomeComponent {
  blogPosts$: Observable<ScullyRoute[]>;

  constructor(private scully: ScullyRoutesService) {
    this.blogPosts$ = this.scully.available$.pipe(
      map(routes => 
        routes
          .filter(route => route.route.startsWith('/blog/'))
          .filter(route => route.published !== false)
          .sort((a, b) => {
            const dateA = new Date(a.date || 0).getTime();
            const dateB = new Date(b.date || 0).getTime();
            return dateB - dateA;
          })
      )
    );
  }
}
src/app/home/home.component.html
<div class="blog-list">
  <article *ngFor="let post of blogPosts$ | async">
    <h2>
      <a [routerLink]="post.route">{{ post.title }}</a>
    </h2>
    <p class="meta">
      <span>{{ post.date | date }}</span>
      <span *ngIf="post.author">by {{ post.author }}</span>
    </p>
    <p>{{ post.description }}</p>
    <div class="tags">
      <span *ngFor="let tag of post.tags" class="tag">{{ tag }}</span>
    </div>
  </article>
</div>

Blog Post Component

The generated blog component displays the markdown content:
src/app/blog/blog.component.ts
import { Component, OnInit } from '@angular/core';
import { ScullyRoutesService, ScullyRoute } from '@scullyio/ng-lib';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-blog',
  templateUrl: './blog.component.html',
  styleUrls: ['./blog.component.css'],
})
export class BlogComponent implements OnInit {
  currentRoute$: Observable<ScullyRoute>;

  constructor(private scully: ScullyRoutesService) {}

  ngOnInit() {
    this.currentRoute$ = this.scully.getCurrent();
  }
}
src/app/blog/blog.component.html
<article *ngIf="currentRoute$ | async as route">
  <header>
    <h1>{{ route.title }}</h1>
    <p class="meta">
      <time>{{ route.date | date }}</time>
      <span *ngIf="route.author">by {{ route.author }}</span>
    </p>
  </header>
  
  <!-- Scully content placeholder -->
  <scully-content></scully-content>
  
  <footer>
    <div class="tags" *ngIf="route.tags">
      <span *ngFor="let tag of route.tags">{{ tag }}</span>
    </div>
  </footer>
</article>

Advanced Features

Syntax Highlighting

Enable code syntax highlighting:
scully.config.ts
import { setPluginConfig } from '@scullyio/scully';
import 'prismjs/components/prism-typescript';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-css';

setPluginConfig('md', { enableSyntaxHighlighting: true });
src/styles.css
/* Import Prism theme */
@import '~prismjs/themes/prism-tomorrow.css';

Custom Metadata

Add any metadata to your frontmatter:
---
title: My Post
author: Jane Doe
date: 2024-03-15
tags: [angular, typescript]
category: tutorials
featuredImage: /assets/images/post-hero.jpg
readingTime: 5 min
---
Access it in your component:
this.currentRoute$.subscribe(route => {
  console.log(route.author);        // 'Jane Doe'
  console.log(route.tags);          // ['angular', 'typescript']
  console.log(route.readingTime);   // '5 min'
});

Post Renderers

Add custom processing for blog posts:
scully.config.ts
export const config: ScullyConfig = {
  routes: {
    '/blog/:slug': {
      type: 'contentFolder',
      postRenderers: ['toc', 'reading-time'],
      slug: {
        folder: './blog',
      },
    },
  },
};

Troubleshooting

Problem: /blog/:slug route returns 404Solutions:
  • Run npx scully --scanRoutes to discover routes
  • Check blog module is imported in app routing
  • Verify markdown files exist in configured folder
  • Check file frontmatter is valid YAML
Problem: Blog post shows raw markdownSolutions:
  • Ensure <scully-content> tag is in blog component template
  • Import ScullyLibModule in blog module
  • Run npx scully to generate static pages
  • Check browser console for errors
Problem: Draft posts appear in productionSolutions:
  • Filter posts by published property: .filter(route => route.published !== false)
  • Set published: false in frontmatter
  • Check deployment doesn’t include ___UNPUBLISHED___ routes
Problem: Code blocks have no syntax highlightingSolutions:
  • Enable highlighting: setPluginConfig('md', { enableSyntaxHighlighting: true })
  • Import Prism.js theme in styles.css
  • Import language components: import 'prismjs/components/prism-typescript'
  • Verify code blocks use proper markdown syntax with language

Next Steps

Working with Markdown

Learn advanced markdown features and frontmatter

Configuration

Customize your Scully configuration

Deployment

Deploy your blog to production

Testing

Test your blog locally before deployment

Build docs developers (and LLMs) love