SkillAgentSearch skills...

After.js

Next.js-like framework for server-rendered React apps built with React Router

Install / Use

/learn @jaredpalmer/After.js
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

repo-banner

After.js

npm bundle size (scoped) npm Known Vulnerabilities Github Actions GitHub version After-status license Discord

If Next.js and React Router had a baby...

Project Goals / Philosophy / Requirements

Next.js is awesome. However, its routing system isn't for me. IMHO React Router is a better foundation upon which such a framework should be built....and that's the goal here:

  • Routes are just components and don't / should not have anything to do with folder structure. Static route configs are fine.
  • Next.js's getInitialProps was/is a brilliant idea.
  • Route-based code-splitting should come for free or be easy to opt into.
  • Route-based transitions / analytics / data loading / preloading etc. , should either come for free or be trivial to implement on your own.

Table of Contents

<!-- prettier-ignore-start --> <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- prettier-ignore-end -->

Getting Started with After.js

After.js enables Next.js-like data fetching with any React SSR app that uses React Router.

Quickstart

You can quickly bootstrap an SSR React app with After.js using Razzle. While Razzle is not required, this documentation assumes you have the tooling setup for an isomorphic React application.

yarn global add create-after-app
create-after-app myapp
cd myapp
yarn start

Refer to Razzle's docs for tooling, babel, and webpack customization.

Data Fetching

For page components, you can add a static async getInitialProps function. This will be called on both initial server render, and then client mounts. Results are made available on this.props.

// ./src/About.js
import React from 'react';
import { NavLink } from 'react-router-dom';

class About extends React.Component {
  static async getInitialProps({ req, res, match }) {
    const stuff = await CallMyApi();
    return { stuff };
  }

  render() {
    return (
      <div>
        <NavLink to="/">Home</NavLink>
        <NavLink to="/about">About</NavLink>
        <h1>About</h1>
        {this.props.stuff}
      </div>
    );
  }
}

export default About;

getInitialProps: (ctx) => Data

Within getInitialProps, you have access to all you need to fetch data on both the client and the server:

  • req?: Request: (server-only) An Express.js request object.
  • res?: Response: (server-only) An Express.js response object.
  • match: React Router's match object.
  • history: React Router's history object.
  • location: (client-only) React Router's location object (you can only use location.pathname on server).
  • scrollToTop: React Ref object that controls scroll behavior when URL changes.

Add Params to getInitialProps: (ctx) => Data

You can extend ctx, and pass your custom params to it. this is useful when you want to fetch some data by condition or store fetched data in a global state managment system (like redux) or you may need to pass those params as props to your component from server.js (e.g result of user agent parsing).

// ./src/server.js
...
try {
  const html = await render({
    req,
    res,
    routes,
    chunks,
    // Anything else you add here will be made available
    // within getInitialProps(ctx)
    // e.g a redux store...
    customThing: 'thing',
  });
  res.send(html);
} catch (error) {
  console.error(error);
  res.json({ message: error.message, stack: error.stack });
}
...

Don't forget to pass your custom params to <After/> in client.js:

// ./src/client.js
...
ensureReady(routes).then(data =>
  hydrate(
    <BrowserRouter>
      {/*
        Anything else you pass to <After/> will be made available
        within getInitialProps(ctx)
        e.g a redux store...
      */}
      <After data={data} routes={routes} customThing="thing" />
    </BrowserRouter>,
    document.getElementById('root')
  )
);
...

Injected Page Props

  • Whatever you have returned in getInitialProps
  • prefetch: (pathname: string) => void - Imperatively prefetch and cache data for a path. Under the hood this will map through your route tree, call the matching route's getInitialProps, store it, and then provide it to your page component. If the user ultimately navigates to that path, the data and component will be ready ahead of time. In the future, there may be more options to control cache behavior in the form of a function or time in milliseconds to keep that data around.
  • refetch: (nextCtx?: any) => void - Imperatively call getInitialProps again
  • isLoading - It shows that if the returned promise from getInitialProps is in the pending state or not

Routing

As you have probably figured out, React Router powers all of After.js's routing. You can use any and all parts of RR.

Parameterized Routing

// ./src/routes.js
import Home from './Home';
import About from './About';
import Detail from './Detail';

// Internally these will become:
// <Route path={path} exact={exact} render={props => <component {...props} data={data} />} />
const routes = [
  {
    path: '/',
    exact: true,
    component: Home,
  },
  {
    path: '/about',
    component: About,
  },
  {
    path: '/detail/:id',
    component: Detail,
  },
];

export default routes;
// ./src/Detail.js
import React from 'react';
import { Route } from 'react-router-dom';

class Detail extends React.Component {
  // Notice that this will be called for
  // /detail/:id
  // /detail/:id/more
  // /detail/:id/other
  static async getInitialProps({ req, res, match }) {
    const item = await CallMyApi(`/v1/item${match.params.id}`);
    return { item };
  }

  render() {
    return (
      <div>
        <h1>Detail</h1>
        {this.props.item}
        <Route
          path="/detail/:id/more"
          exact
          render={() => <div>{this.props.item.more}</div>}
        />
        <Route
          path="/detail/:id/other"
          exact
          render={() => <div>{this.props.item.other}</div>}
        />
      </div>
    );
  }
}

export default Detail;

Client Only Data and Routing

In some parts of your application, you may not need server data fetching at all (e.g. settings). With After.js, you just use React Router 4 as you normally would in client land: You can fetch data (in componentDidMount) and do routing the same exact way.

Transition Behavior

By default, after.js will wait for getInitialProps to get resolved or rejected, so when the getInitialProps job is complete, it will show the next page. We call this behavior blocked.

You may want to show the next page with a skeleton or a spinner while getInitialProps is pending. We call this behavior instant.

you can switch to instant behavior by passing a prop to <After />.

// ./src/client.js

// transitionBehavior = blocked | instant

ensureReady(routes).then(data =>
  hydrate(
    <BrowserRouter>
      <After data={data} routes={routes} transitionBehavior="instant" />
    </BrowserRouter>,
    document.getElementById('root')
  )
);

Dynamic 404 and Redirects

404 Page

React Router can detect No Match (404) Routes and show a fallback component, you can define your custom fallback component in routes.js file.

// ./src/routes.js

import React from 'react';
import Home from './Home';
import Notfound from './Notfound';
import { asyncComponent } from '@jaredpalmer/after';

export default [
  // normal route
  {
    path: '/',
    exact: true,
    component: Home,
  },
  // 404 route
  {
    // there is no need to declare path variable
    // react router will pick this component as fallba
View on GitHub
GitHub Stars4.1k
CategoryDevelopment
Updated2d ago
Forks197

Languages

TypeScript

Security Score

100/100

Audited on Mar 25, 2026

No findings