Skip to main content

Deployment Guide

Scully generates static HTML files that can be deployed to any static hosting service. This guide covers deployment to popular platforms and best practices.

Pre-Deployment Checklist

  • Angular app builds successfully (ng build --configuration production)
  • Scully runs without errors (npx scully --scanRoutes)
  • All routes are generated in dist/static
  • Static site tested locally (npx scully serve)
  • SEO metadata configured (titles, descriptions, meta tags)
  • 404 page configured
  • Analytics and tracking set up (optional)
  • Images optimized
  • Build scripts added to package.json

Build Commands

Basic Build

# Build Angular app
ng build --configuration production

# Generate static pages with Scully
npx scully --scanRoutes

Single Command Build

Add to package.json:
{
  "scripts": {
    "build": "ng build --configuration production",
    "scully": "npx scully --scanRoutes",
    "build:prod": "npm run build && npm run scully",
    "serve:static": "npx scully serve"
  }
}
Then run:
npm run build:prod

Output Directory

Scully generates static files in the outDir (default: ./dist/static):
dist/
├── static/                 # Deploy this folder!
│   ├── index.html
│   ├── about/
│   │   └── index.html
│   ├── blog/
│   │   ├── post-1/
│   │   │   └── index.html
│   │   └── post-2/
│   │       └── index.html
│   └── assets/
│       └── (all assets)
└── your-app-name/         # Angular build (not needed for static hosting)
Deploy the dist/static folder, not the dist/your-app-name folder.

Netlify

Netlify is a popular choice for JAMstack sites with automatic deployments from Git.
1

Create netlify.toml

Create a netlify.toml file in your project root:
[build]
  command = "npm run build:prod"
  publish = "dist/static"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-XSS-Protection = "1; mode=block"
    X-Content-Type-Options = "nosniff"
2

Connect Repository

  1. Go to Netlify
  2. Click “New site from Git”
  3. Connect your repository (GitHub, GitLab, Bitbucket)
  4. Select your repository
3

Configure Build Settings

Netlify will auto-detect settings from netlify.toml, but verify:
  • Build command: npm run build:prod
  • Publish directory: dist/static
  • Node version: Set in netlify.toml or UI
Add environment variable:
[build.environment]
  NODE_VERSION = "18"
4

Deploy

Click “Deploy site”. Netlify will:
  • Install dependencies
  • Run build command
  • Deploy dist/static folder
  • Provide a URL

Netlify CLI

Deploy manually with Netlify CLI:
# Install Netlify CLI
npm install -g netlify-cli

# Build your site
npm run build:prod

# Deploy
netlify deploy --prod --dir=dist/static

Vercel

Vercel provides excellent Angular support with zero configuration.
1

Create vercel.json

Create vercel.json in your project root:
{
  "version": 2,
  "buildCommand": "npm run build:prod",
  "outputDirectory": "dist/static",
  "routes": [
    {
      "src": "/(.*)",
      "dest": "/"
    }
  ]
}
2

Deploy via Git

  1. Go to Vercel
  2. Import your Git repository
  3. Vercel auto-detects Angular
  4. Override settings if needed:
    • Build Command: npm run build:prod
    • Output Directory: dist/static
  5. Click “Deploy”

Vercel CLI

# Install Vercel CLI
npm install -g vercel

# Build
npm run build:prod

# Deploy
vercel --prod

GitHub Pages

Host your static site for free on GitHub Pages.
1

Install angular-cli-ghpages

npm install -g angular-cli-ghpages
2

Configure Base Href

Update scully.config.ts for GitHub Pages subdirectory:
import { ScullyConfig } from '@scullyio/scully';

export const config: ScullyConfig = {
  projectRoot: './src',
  projectName: 'my-app',
  outDir: './dist/static',
  routes: {
    '/': {
      type: 'default',
      baseHref: '/your-repo-name/',  // Important for GitHub Pages
    },
  },
};
Build with correct base href:
ng build --configuration production --base-href /your-repo-name/
npx scully
3

Deploy

# Deploy dist/static to gh-pages branch
npx angular-cli-ghpages --dir=dist/static
Or manually:
cd dist/static
git init
git add -A
git commit -m 'Deploy'
git push -f [email protected]:username/repo.git master:gh-pages
4

Enable GitHub Pages

  1. Go to repository Settings → Pages
  2. Select gh-pages branch
  3. Click Save
  4. Access at https://username.github.io/repo-name/

Firebase Hosting

Google’s Firebase offers fast global CDN hosting.
1

Install Firebase CLI

npm install -g firebase-tools
2

Initialize Firebase

firebase login
firebase init hosting
When prompted:
  • Public directory: dist/static
  • Configure as SPA: Yes
  • Overwrite index.html: No
3

Configure firebase.json

Update firebase.json:
{
  "hosting": {
    "public": "dist/static",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [
      {
        "source": "**",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "public, max-age=3600"
          }
        ]
      }
    ]
  }
}
4

Deploy

npm run build:prod
firebase deploy

AWS S3 + CloudFront

Host on AWS for enterprise-grade infrastructure.
1

Create S3 Bucket

aws s3 mb s3://your-bucket-name
Enable static website hosting:
aws s3 website s3://your-bucket-name --index-document index.html
2

Configure Bucket Policy

Create bucket-policy.json:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
Apply:
aws s3api put-bucket-policy --bucket your-bucket-name --policy file://bucket-policy.json
3

Upload Files

npm run build:prod
aws s3 sync dist/static s3://your-bucket-name --delete
4

Set Up CloudFront (Optional)

  1. Create CloudFront distribution
  2. Set origin to S3 bucket
  3. Configure caching rules
  4. Add custom domain (optional)

Docker

Containerize your static site with nginx.
1

Create Dockerfile

FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build:prod

FROM nginx:alpine
COPY --from=builder /app/dist/static /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
2

Create nginx.conf

server {
  listen 80;
  server_name localhost;
  root /usr/share/nginx/html;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }

  # Cache static assets
  location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }

  # Security headers
  add_header X-Frame-Options "SAMEORIGIN" always;
  add_header X-Content-Type-Options "nosniff" always;
  add_header X-XSS-Protection "1; mode=block" always;
}
3

Build and Run

# Build image
docker build -t my-scully-app .

# Run container
docker run -p 8080:80 my-scully-app

CI/CD Pipelines

GitHub Actions

.github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install Dependencies
        run: npm ci
      
      - name: Build
        run: npm run build:prod
      
      - name: Deploy to Netlify
        uses: nwtgck/actions-netlify@v2
        with:
          publish-dir: './dist/static'
          production-branch: main
        env:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

GitLab CI

.gitlab-ci.yml
image: node:18

stages:
  - build
  - deploy

cache:
  paths:
    - node_modules/

build:
  stage: build
  script:
    - npm ci
    - npm run build:prod
  artifacts:
    paths:
      - dist/static
    expire_in: 1 week

deploy:
  stage: deploy
  script:
    - npm install -g netlify-cli
    - netlify deploy --prod --dir=dist/static --auth=$NETLIFY_AUTH_TOKEN --site=$NETLIFY_SITE_ID
  only:
    - main

Production Optimizations

Angular Build Optimizations

angular.json
{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            }
          }
        }
      }
    }
  }
}

Scully Production Config

scully.config.ts
import { ScullyConfig } from '@scullyio/scully';
import { removeScripts } from '@scullyio/scully-plugin-remove-scripts';

const isProd = process.env.NODE_ENV === 'production';

export const config: ScullyConfig = {
  projectRoot: './src',
  outDir: './dist/static',
  
  // Optimize for production
  maxRenderThreads: isProd ? 8 : 4,
  ignoreResourceTypes: isProd ? ['image', 'font', 'media'] : [],
  
  defaultPostRenderers: isProd 
    ? [removeScripts, 'seoHrefOptimise']
    : ['seoHrefOptimise'],
    
  routes: {
    // Your routes
  },
};

Troubleshooting

Problem: Page refreshes return 404Solution: Configure server rewrites:
  • Netlify: Add redirects in netlify.toml
  • Vercel: Add routes in vercel.json
  • nginx: Use try_files $uri $uri/ /index.html
  • Firebase: Add rewrites in firebase.json
Problem: Assets not loading, routing brokenSolution:
  • Build with correct base href: ng build --base-href /path/
  • Update Scully config baseHref for routes
  • Ensure assets paths are absolute: /assets/...
Problem: Build succeeds locally but fails in CISolution:
  • Match Node.js version between local and CI
  • Use npm ci instead of npm install
  • Clear npm cache: npm cache clean --force
  • Check memory limits (increase if needed)
  • Review error logs for missing dependencies
Problem: Static pages load slowlySolution:
  • Enable CDN on hosting platform
  • Optimize images (use WebP, proper sizing)
  • Enable gzip/brotli compression
  • Set cache headers for static assets
  • Remove unused Angular bundles with removeScripts
  • Lazy load images

Post-Deployment Checklist

  • All pages accessible and loading correctly
  • Navigation works between pages
  • Assets (images, fonts, CSS) loading
  • SEO meta tags present (view source)
  • Analytics tracking working
  • 404 page displays correctly
  • Mobile responsive design working
  • HTTPS enabled
  • Custom domain configured (if applicable)
  • Sitemap accessible
  • robots.txt configured
  • Performance tested (Lighthouse, PageSpeed)

Next Steps

Testing

Test your site before deployment

Configuration

Optimize your Scully configuration

Plugins

Add SEO and performance plugins

API Reference

Full ScullyConfig API reference

Build docs developers (and LLMs) love