npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gremllm/nextjs

v0.7.0

Published

Next.js HOC for serving LLM-optimized markdown versions of web content

Readme

@gremllm/nextjs

Next.js integration for gremllm - serve LLM-optimized markdown versions of your web pages by adding ?gremllm to any URL.

What is this?

This package enables your Next.js application to serve token-optimized markdown for LLM consumption. When a request comes in for /about?gremllm, it automatically:

  1. Detects the ?gremllm parameter in middleware
  2. Fetches the page HTML (SSR/SSG)
  3. Strips LLM-irrelevant content (navigation, footers, ads, scripts, styles, etc.)
  4. Converts to clean, semantic markdown
  5. Returns optimized markdown (~50-80% token reduction vs raw HTML)

Features

  • 🚀 Zero config - Works out of the box with sensible defaults
  • 🎯 Query parameter based - Add ?gremllm to any URL
  • Native performance - FFI bindings to Rust library via koffi for fast conversion
  • 📝 Full markdown output - Clean, semantic markdown optimized for LLMs
  • 🔧 Configurable - Customize which elements to strip
  • 📦 Automatic binary downloads - Platform-specific binaries downloaded on install
  • 💪 TypeScript - Full type safety included

Installation

npm install @gremllm/nextjs koffi
# or
yarn add @gremllm/nextjs koffi

Important: koffi must be installed as a peer dependency for native module support.

The postinstall script will automatically download the correct binary for your platform (Linux, macOS, Windows) from the gremllm/lib releases.

Quick Start

1. Update your Next.js config

Wrap your config with withGremllm:

// next.config.js
const { withGremllm } = require('@gremllm/nextjs');

const nextConfig = {
  // Your existing Next.js config
};

module.exports = withGremllm(nextConfig, {
  // Optional: customize elements to strip
  elementsToStrip: ['header', 'footer', 'nav'],
  debug: false,
});

2. Create API Route

Create app/api/gremllm/route.ts and simply export the pre-built handler:

// app/api/gremllm/route.ts
export { GET, runtime } from '@gremllm/nextjs/route';

That's it! The handler includes:

  • ✅ Node.js runtime configuration
  • ✅ URL extraction from middleware headers
  • ✅ HTML fetching with proper User-Agent
  • ✅ Content-type validation
  • ✅ Markdown conversion using your config
  • ✅ Error handling
  • ✅ Cache headers

3. Add Middleware

Create or update middleware.ts at your project root.

Option A: Simple (recommended for new projects)

Just export the pre-built middleware:

// middleware.ts
export { middleware, config } from '@gremllm/nextjs/middleware';

Option B: Compose with existing middleware

If you already have custom middleware logic, use gremllmMiddleware:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { gremllmMiddleware } from '@gremllm/nextjs/middleware';

export function middleware(request: NextRequest) {
  // Try gremllm first
  const gremllmResponse = gremllmMiddleware(request);
  if (gremllmResponse) {
    return gremllmResponse;
  }

  // Your custom middleware logic here
  // e.g., authentication, redirects, etc.

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!api/|_next/static|_next/image|favicon.ico).*)'],
};

4. Done! 🎉

Visit any page with ?gremllm:

# Regular HTML
curl https://yoursite.com/about

# LLM-optimized markdown
curl https://yoursite.com/about?gremllm

How It Works

Request: /about?gremllm
    ↓
Middleware (Edge Runtime): Detects ?gremllm
    ↓
Rewrite: /api/gremllm (with URL in header)
    ↓
API Route (Node.js Runtime): Fetches HTML, converts to markdown
    ↓
Native Library (koffi FFI): Fast Rust-based conversion
    ↓
Response: Clean markdown (text/markdown)

Architecture Rationale:

  • Middleware runs in Edge Runtime (can't use native modules)
  • API Route runs in Node.js Runtime (can use koffi/native modules)
  • This split architecture gives us both speed and native module support

Configuration

Pass options to withGremllm:

// next.config.js
module.exports = withGremllm(nextConfig, {
  elementsToStrip: ['header', 'footer'], // Additional elements to strip
  debug: true, // Enable debug logging
});

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | elementsToStrip | string[] | [] | Additional HTML elements to remove (added to defaults) | | debug | boolean | false | Enable debug logging |

Default Stripped Elements

The following elements are always stripped:

  • nav, aside, footer, header - Navigation and layout
  • script, style, noscript - Code and styling
  • svg, iframe - Embedded content

Environment Variables

| Variable | Type | Default | Description | |----------|------|---------|-------------| | GREMLLM_BASE_URL | string | undefined | Override base URL for internal fetches. Useful when running behind a proxy/load balancer with SSL termination. Example: http://localhost:3000 |

Use case: When your application is behind a load balancer or ingress with HTTPS, but runs on HTTP internally, set this to avoid SSL errors:

# In your deployment
GREMLLM_BASE_URL=http://localhost:3000

This will make gremllm fetch pages using http://localhost:3000/path instead of the external https://yourdomain.com/path.

Platform Support

The native gremllm library is automatically downloaded for:

  • macOS: ARM64 (Apple Silicon) and AMD64 (Intel)
  • Linux: ARM64 and AMD64
  • Windows: AMD64

Binaries are pulled from gremllm/lib releases.

API Reference

withGremllm(nextConfig, gremllmConfig)

Higher-order function that wraps your Next.js configuration.

Parameters:

  • nextConfig (object): Your Next.js configuration
  • gremllmConfig (object, optional):
    • elementsToStrip (string[]): HTML elements/selectors to remove
    • debug (boolean): Enable debug logging

Returns: Enhanced Next.js configuration with webpack externals configured

convert(html, elementsToStrip)

Convert HTML to markdown with custom element stripping.

import { convert } from '@gremllm/nextjs';

const markdown = convert(html, ['header', 'footer']);

convertWithDefaults(html)

Convert HTML to markdown using default element stripping.

import { convertWithDefaults } from '@gremllm/nextjs';

const markdown = convertWithDefaults(html);

Default elements stripped: nav, aside, footer, header, script, style, noscript, svg, iframe

Middleware Exports

middleware and config from @gremllm/nextjs/middleware

Pre-built middleware for standalone use:

export { middleware, config } from '@gremllm/nextjs/middleware';

gremllmMiddleware(request) from @gremllm/nextjs/middleware

Composable middleware function for integration with existing middleware:

import { gremllmMiddleware } from '@gremllm/nextjs/middleware';

export function middleware(request: NextRequest) {
  const gremllmResponse = gremllmMiddleware(request);
  if (gremllmResponse) return gremllmResponse;

  // Your custom logic
  return NextResponse.next();
}

Parameters:

  • request (NextRequest): The incoming request

Returns: NextResponse | null - Returns a rewrite response if ?gremllm is present, null otherwise

Troubleshooting

"Cannot find module 'koffi'"

Make sure koffi is installed:

npm install koffi

"Native module not found"

The binary downloads automatically during npm install. If it fails, manually run:

npm run postinstall

Or manually download from releases and place in node_modules/@gremllm/nextjs/binaries/.

"Edge runtime error"

Ensure your API route has export const runtime = 'nodejs' - native modules require Node.js runtime, not Edge runtime.

Middleware not running

  • Check middleware.ts is at project root
  • Verify config.matcher is correct
  • Ensure /api/gremllm is excluded from matcher

Requirements

  • Next.js 13.0.0 or higher (App Router)
  • Node.js 18.0.0 or higher
  • Supported platforms: macOS (Intel/Apple Silicon), Linux (x64/ARM64), Windows (x64)

Examples

See the examples/ directory for complete working examples.

Advanced Usage

Custom Route Handler

If you need to customize the route behavior (add authentication, custom caching, etc.), create your own handler:

// app/api/gremllm/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { convertWithDefaults } from '@gremllm/nextjs';

export const runtime = 'nodejs';

export async function GET(request: NextRequest) {
  // Add authentication
  const apiKey = request.headers.get('x-api-key');
  if (!isValidApiKey(apiKey)) {
    return new NextResponse('Unauthorized', { status: 401 });
  }

  const targetUrl = request.headers.get('x-gremllm-url');
  if (!targetUrl) {
    return NextResponse.json({ error: 'Missing target URL' }, { status: 400 });
  }

  try {
    const response = await fetch(targetUrl);
    const html = await response.text();
    const markdown = convertWithDefaults(html);

    // Custom caching or rate limiting logic here

    return new NextResponse(markdown, {
      status: 200,
      headers: {
        'Content-Type': 'text/markdown; charset=utf-8',
        'Cache-Control': 'public, max-age=3600',
      },
    });
  } catch (error) {
    return NextResponse.json({ error: 'Internal error' }, { status: 500 });
  }
}

Custom Middleware Logic

Extend the middleware with custom logic:

import { gremllmMiddleware } from '@gremllm/nextjs/middleware';

export function middleware(request: NextRequest) {
  // Add authentication before gremllm
  const apiKey = request.headers.get('x-api-key');
  if (request.nextUrl.searchParams.has('gremllm') && !isValidApiKey(apiKey)) {
    return new NextResponse('Unauthorized', { status: 401 });
  }

  // Handle gremllm conversion
  const gremllmResponse = gremllmMiddleware(request);
  if (gremllmResponse) {
    return gremllmResponse;
  }

  return NextResponse.next();
}

Programmatic Conversion

Use the converter directly in your code:

import { convert, convertWithDefaults } from '@gremllm/nextjs';

// With custom elements
const optimized = convert(html, ['header', 'footer', 'nav']);

// With defaults
const optimized = convertWithDefaults(html);

Security Considerations

  • ✅ Only strips HTML elements, doesn't add content
  • ✅ Uses native library with memory safety (Rust)
  • ✅ No external network calls (except fetching your own pages)
  • ⚠️ Increases server load (renders + converts on every ?gremllm request)
  • ⚠️ Consider rate limiting for production
  • ⚠️ Consider caching converted pages

Performance Tips

  1. Add caching: Cache converted markdown to avoid repeated conversion
  2. Rate limiting: Limit ?gremllm requests per IP
  3. CDN: Cache markdown responses at CDN level
  4. ISR/SSG: Use Incremental Static Regeneration where possible

Related Projects

  • gremllm/lib - Core Rust library for HTML conversion
  • koffi - Fast and easy-to-use Node.js FFI library

Contributing

Issues and PRs welcome! This is an open-source project.

Help Wanted:

  • [ ] Add caching layer example
  • [ ] Performance benchmarks
  • [ ] Integration tests
  • [ ] Streaming support for large pages

License

MIT

Links