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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@freshcells/next-static-components

v2.2.5

Published

@freshcells/next-static-components ----------------------------------

Downloads

476

Readme

@freshcells/next-static-components

semantic-release npm

Experimental Utility to allow rendering parts of a next.js application (e.g. Header & Footer) for embedding into third party applications.

Install

yarn add @freshcells/next-static-components

Requirements

  • nextjs, tested with 12.x and 13.x. Nextjs 13.x requires version >=13.2.3
  • node >= 16.x, recommended is node >= 18.x

Usage

Add the following script to your application

{
  "scripts": {
    "build-static": "cross-env BABEL_ENV=static NODE_OPTIONS='--experimental-import-meta-resolve' next-static-components ./static-page/entrypoint.tsx"
  }
}

Command args

  • --env cacheSuffix=custom-cache-suffix, you can provide a custom cache suffix, this use cacheSuffix as subdirectory under the default cache directory (.next-static/cache/webpack/yourSuffix).

This command will dump all compilation output into a new folder called .next-static. Make sure you include this folder into your build process / Dockerfile.

Caching

Webpack is configured to cache, so you may want to integrate the .next-static/cache/webpack folder into your CI/CD process.

Babel configuration

If not exists, please create a babel.config.js or .babelrc with the following:

module.exports = {
    // if defined, please move `next/babel` from the generic definitions into it's own preset
    env: {
        // used for nextjs (e.g. dev, build etc.)
        nextjs: {
            presets: ['next/babel'],
        },
        // adapted preset to be used for the static component build
        static: {
            presets: ['@freshcells/next-static-components/babel'],
        }
    }
}

NextJS configuration / custom loaders & webpack

We try to infer most configuration from your nextJS configuration. In case you require to exclude certain configuration from the static build you can use the process.env.IS_NEXT_STATIC_BUILD env to detect a static build.

Entrypoint

We require a single entrypoint where you define all your static components. In the example above we created a file static-page/entrypoint.tsx.

It should export a single function in the following form.

import React from 'react'
import type {Entrypoint, WrapperProps} from '@freshcells/next-static-components'

// .. import anything from your application ..

interface YourContext {
    someData: string
}

interface YourProps {
    someData: string
}

const Header = (props: YourProps) => {
    return <p>My Header</p>
}

const Footer = (props: YourProps) => {
    return <p>My Footer</p>
}

const entry: Entrypoint<YourProps, YourContext> = async (context: YourContext) => {
    // initialize your application here
    return {
        // Will be passed to each component
        props: context,
        // A list of components to be mounted on a page (with different roots)
        components: [Header, Footer],
        // optional wrapper component that lets you customize the markup.
        // By default it will render both components after another (always in a wrapper div)
        wrapper: function MyWrapper({components}: WrapperProps) {
            const [header, footer] = components
            return (
                <div>
                    {header}
                    <div>something in between</div>
                    {footer}
                </div>
            )
        },
        // items that will be placed in the `<head />` area
        additionalHeadElement: (
            <>
                <title>A title</title>
            </>
        )
    }
}

export default entry

Expose your static components

Create a new API route, e.g. in pages/api/static/[...serve].ts. It's very important to use [...xxxx] as we will also serve assets and the frontend js bundles from this path.

import {serve} from '@freshcells/next-static-components'

export default serve(
    /* context provider, all data will be passed to both frontend and backend */
    async (req, res) => ({someData: 'myValue'}),
    /* options, static or cb */
    /* or cb async (req, res) => ({ locale: req.query.locale }) */
    {
        // the following options are for prod only, will serve from localhost in development
        // the domain where all assets are served from 
        assetPrefix: 'https://your-domain.com',
        // the prefix that should be used to render links. Has to be a valid URL.
        linkPrefix: 'https://your-main-applications-domain.com',

        locale: 'de' // (defaults to the `defaultLocale` set or `en` if no i18n config present)
    }
)

| Option | Description | |---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | assetPrefix | The prefix to add for asset generation (e.g. a cdn url). If omitted will use the relative path | | linkPrefix | Prefix to add to any link, if omitted will use a relative path (relative to /) | | locale | The locale to use to render the page. Will use the configured defaultLocale if omitted. Other settings like locales will be inferred from the nextJS configuration |

Run yarn build-static and start your application (you can also run it after). If you now navigate to http://localhost:3000/api/static/render you should see your rendered components.

Query Parameters

You can provide your own query parameters and customize both the provided context and options ( e.g. you could make the locale configurable through the url)

Example:
export default serve(
    async (req) => {
        const myQuery = req.query?.yourQuery
        // ... do something with myQuery
        return {}
    },
    async (req) =>
        ({linkPrefix: 'https://some-domain.com', locale: req.query?.locale as string || 'en-gb'})
)

Development

Important: There is currently no hot-reload or develop mode for static generation, so you have to rebuild if you make any change.

Ignore

Add .next-static to you .gitignore file.

Restrictions

This is a very experimental package to support a certain use case most projects probably won't have. Not all nextJS features might be supported - and the output of the bundle is very different to the main application. Many optimizations are not applied. The main difference is that we use @loadable to resolve dynamic packages (defined with next/dynamic).

Router

The client side router is not created due to inaccessible singleton instances inside a module ( see, https://github.com/vercel/next.js/blob/98b43a07094e6df2bd40cf0e190708751ead3537/packages/next/src/client/router.ts#L153-L153) so any calls to it will result in an exception.

You may use only <Link />, useRouter() etc. but do not access Router.push, Router.asPath methods directly (e.g. from import Router from 'next/router')

next/head

Modification of the head element is unsupported. The usage within the static tree is a no-op.

next/dynamic

As we replace the dynamic module resolution from next/dynamic with @loadable/component, there are certain restrictions to the API.

All options (e.g. ssr or loader are supported).

Naming

Only dynamic as import name is supported.

Do:

import dynamic from 'next/dynamic'

Do-Not:

import myDynamic from 'next/dynamic'
Promise chaining

You cannot chain the import(...) promise. If you need to map the result to a component you have to map that inside the imported file. So you might need to refactor your implementation. As this is a very small tradeoff we did not invest time in making the API 100% compatible.

Do:

import dynamic from 'next/dynamic'

const MyDynamicComponent = dynamic(() => import('./MyComponent'))

Do-Not:

import dynamic from 'next/dynamic'

// the `then` part will never be called on the server side, so it might return not a component but something else (based on your implementation)
const MyDynamicComponent = dynamic(() => import('./myData').then(data => mapToComponent(data)))