skier. v1.0.0

generatePaginatedItemsTask

Generate multiple HTML pages from a data array with built-in pagination controls.


When to Use

✅ Use generatePaginatedItemsTask when you need:

  • Paginated blog archives
  • Activity timelines split across pages
  • Any large dataset that should be chunked

❌ Use generateItemsTask instead when:

  • Each item needs its own detail page
  • Data comes from Markdown files

Quick Start

generatePaginatedItemsTask({
  dataVar: '${posts}',
  itemsPerPage: 10,
  template: 'src/pages/blog.html',
  partialsDir: 'src/partials',
  outDir: 'public',
  basePath: '/blog',
})

Output files:

public/
├── blog.html           # Page 1
└── blog/
    └── page/
        ├── 2.html      # Page 2
        └── 3.html      # Page 3

Configuration

Option Type Required Description
dataFile string Path to JSON file
dataVar string Pipeline variable using ${varName} syntax
dataKey string Key to extract array from JSON object
itemsPerPage number Items per page
template string Handlebars template path
partialsDir string Partials directory
outDir string Output directory
basePath string URL base path (for links)
outputVar string Template variable name for items (default: 'items')
paginationVar string Template variable for pagination (default: 'pagination')
itemTransformFn function Transform each item
additionalVarsFn function Add extra template variables

⚡ Exactly one of dataFile or dataVar is required.


Type signature

The exact GeneratePaginatedItemsConfig interface, transcluded from source so it stays in step with the shipping code:

export interface GeneratePaginatedItemsConfig {
  /**
   * Path to a JSON file containing the data.
   * Mutually exclusive with dataVar.
   */
  dataFile?: string;

/**

  • Variable name from globals to use as data source.
  • Use ${varName} syntax (e.g., '${thoughtsList}').
  • Mutually exclusive with dataFile. */ dataVar?: string;

/**

  • Optional key within the JSON data to read the array from.
  • E.g., 'timeline' to read from data.timeline. */ dataKey?: string;

/** Number of items per page */ itemsPerPage: number;

/** Path to the Handlebars template for each page */ template: string;

/** Directory containing Handlebars partials */ partialsDir: string;

/** Output directory for generated pages */ outDir: string;

/**

  • Base path for URLs and output files.
  • E.g., '/life-fitness' results in:
    • /life-fitness.html (page 1)
    • /life-fitness/page/2.html (page 2) */ basePath: string;

/**

  • Variable name for the items array in templates.
  • Defaults to 'items'. */ outputVar?: string;

/**

  • Variable name for the pagination metadata in templates.
  • Defaults to 'pagination'. */ paginationVar?: string;

/**

  • Optional function to transform each item before passing to the template.
  • Receives the item and its index. */ itemTransformFn?: (item: unknown, index: number) => unknown;

/**

  • Optional function to inject additional variables per page.
  • Receives page context including pageNumber, totalPages, and items. */ additionalVarsFn?: (args: { pageNumber: number; totalPages: number; items: unknown[]; }) => Record<string, unknown> | Promise<Record<string, unknown>>; }

Every task's config type together: API Reference → Config Interfaces.


Data Sources

From pipeline globals (recommended):

generateItemsTask({ outputVar: 'posts' }),
generatePaginatedItemsTask({
  dataVar: '${posts}',
  // ...
})

From JSON file:

generatePaginatedItemsTask({
  dataFile: './data/activities.json',
  dataKey: 'timeline',  // Extract data.timeline
  // ...
})

Pagination Object

Your template receives a pagination object:

{
  currentPage: 2,
  totalPages: 5,
  totalItems: 47,
  itemsPerPage: 10,

  hasNext: true,
  hasPrev: true,

  nextUrl: '/blog/page/3',
  prevUrl: '/blog',          // Page 1 uses base path
  firstUrl: '/blog',
  lastUrl: '/blog/page/5',

  pages: [
    { number: 1, url: '/blog', isCurrent: false },
    { number: 2, url: '/blog/page/2', isCurrent: true },
    { number: 3, url: '/blog/page/3', isCurrent: false },
    // ...
  ]
}

Template Example

<h1>Blog Archive</h1>

{{#each items}}
  <article>
    <h2><a href="{{this.link}}">{{this.title}}</a></h2>
    <time>{{this.formattedDate}}</time>
    <p>{{this.excerpt}}</p>
  </article>
{{/each}}

{{#if pagination.totalPages}}
<nav class="pagination">
  {{#if pagination.hasPrev}}
    <a href="{{pagination.prevUrl}}" rel="prev">← Newer</a>
  {{/if}}

  {{#each pagination.pages}}
    {{#if this.isCurrent}}
      <span class="current">{{this.number}}</span>
    {{else}}
      <a href="{{this.url}}">{{this.number}}</a>
    {{/if}}
  {{/each}}

  {{#if pagination.hasNext}}
    <a href="{{pagination.nextUrl}}" rel="next">Older →</a>
  {{/if}}
</nav>
{{/if}}

Transforming Items

Format data before rendering:

generatePaginatedItemsTask({
  // ...
  itemTransformFn: (item) => ({
    ...item,
    formattedDate: new Date(item.date).toLocaleDateString('en-GB', {
      day: 'numeric',
      month: 'long',
      year: 'numeric',
    }),
  }),
})

Additional Page Variables

Inject extra data per page:

generatePaginatedItemsTask({
  // ...
  additionalVarsFn: ({ pageNumber, totalPages, items }) => ({
    pageTitle: pageNumber === 1
      ? 'Blog'
      : `Blog - Page ${pageNumber} of ${totalPages}`,
    isFirstPage: pageNumber === 1,
  }),
})

Real-World Example

Activity timeline with date grouping:

generatePaginatedItemsTask({
  dataVar: '${activities}',
  itemsPerPage: 20,
  template: 'src/pages/activity-timeline.html',
  partialsDir: 'src/partials',
  outDir: 'public',
  basePath: '/activity',
  outputVar: 'activities',

  itemTransformFn: (activity) => ({
    ...activity,
    icon: getActivityIcon(activity.type),
    formattedDate: formatDate(activity.date),
  }),

  additionalVarsFn: ({ pageNumber, items }) => {
    // Group by month for this page
    const byMonth = items.reduce((acc, item) => {
      const month = item.date.slice(0, 7);
      (acc[month] = acc[month] || []).push(item);
      return acc;
    }, {});

    return {
      pageTitle: `Activity${pageNumber > 1 ? ` - Page ${pageNumber}` : ''}`,
      monthGroups: Object.entries(byMonth),
    };
  },
})

Edge Cases

Situation Behavior
0 items Generates page 1 with empty items array
Items ≤ itemsPerPage Single page, hasNext: false, hasPrev: false
Missing dataVar Empty array, logs warning
Missing dataKey Empty array if key not found in JSON

Common Mistakes

Confusing basePath and outDir:

{
  outDir: 'public',        // Filesystem path
  basePath: '/blog',       // URL path (for links!)
}

Expecting sorted data: Items are NOT sorted automatically. Sort in dataFile, via itemTransformFn, or in a prior task.

Using wrong variable syntax:

dataVar: 'posts',      // ❌ Wrong
dataVar: '${posts}',   // ✅ Correct