Convert Figma logo to code with AI

jaredpalmer logoafter.js

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

4,118
200
4,118
20

Top Related Projects

132,759

The React Framework

31,388

Build Better Websites. Create modern, resilient user experiences with web fundamentals.

55,893

The best React-based framework with performance, scalability and security built in.

19,439

web development, streamlined

57,427

The Intuitive Vue Framework.

17,637

RedwoodGraphQL

Quick Overview

After.js is a Next.js-inspired React framework for building server-rendered React applications with an emphasis on dynamic imports, prefetching, and granular page-level code splitting. It provides a familiar development experience for developers already familiar with Next.js, while offering additional features and flexibility.

Pros

  • Server-side Rendering (SSR): After.js provides server-side rendering out of the box, improving initial load times and SEO.
  • Dynamic Imports: After.js supports dynamic imports, allowing for granular code splitting and improved performance.
  • Prefetching: After.js automatically prefetches resources for faster navigation between pages.
  • Flexibility: After.js is more flexible than Next.js, allowing for more customization and control over the application structure.

Cons

  • Smaller Community: After.js has a smaller community compared to Next.js, which may mean fewer resources and third-party integrations.
  • Steeper Learning Curve: While similar to Next.js, After.js has its own set of concepts and conventions that developers need to learn.
  • Potential Compatibility Issues: As a newer framework, After.js may have more compatibility issues with third-party libraries and tools.
  • Limited Documentation: The documentation for After.js, while improving, is not as comprehensive as that of more established frameworks.

Code Examples

// pages/index.js
import React from 'react';
import { withRouter } from 'after/router';

const Home = ({ router }) => (
  <div>
    <h1>Welcome to the Home Page</h1>
    <p>The current path is: {router.pathname}</p>
  </div>
);

export default withRouter(Home);

This example demonstrates a basic page component in After.js, using the withRouter higher-order component to access the current route information.

// pages/about.js
import React from 'react';
import { Link } from 'after/link';

const About = () => (
  <div>
    <h1>About Page</h1>
    <p>This is the About page.</p>
    <Link href="/">
      <a>Go to Home</a>
    </Link>
  </div>
);

export default About;

This example shows how to use the Link component from After.js to create a link between pages, similar to the Link component in Next.js.

// pages/users/[id].js
import React from 'react';
import { withRouter } from 'after/router';

const UserPage = ({ router }) => (
  <div>
    <h1>User Page</h1>
    <p>User ID: {router.query.id}</p>
  </div>
);

export default withRouter(UserPage);

This example demonstrates how to handle dynamic routes in After.js, using the withRouter higher-order component to access the route parameters.

Getting Started

To get started with After.js, follow these steps:

  1. Install the necessary dependencies:
npm install after react react-dom
  1. Create a new file pages/index.js with the following content:
import React from 'react';

const Home = () => (
  <div>
    <h1>Welcome to the After.js App</h1>
    <p>This is the home page.</p>
  </div>
);

export default Home;
  1. Create a new file server.js with the following content:
const { createServer } = require('http');
const { join } = require('path');
const { parse } = require('url');
const { renderToString } = require('react-dom/server');
const { default: After } = require('after');

const app = new After({
  pages: join(__dirname, 'pages'),
});

createServer((req, res) => {
  const { pathname, query } = parse(req.url, true);
  app.render(req, res, pathname, query);
}).listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});
  1. Start the

Competitor Comparisons

132,759

The React Framework

Pros of Next.js

  • Larger community and ecosystem, with more resources and third-party integrations
  • Built-in image optimization and automatic code splitting
  • Seamless integration with Vercel's hosting platform

Cons of Next.js

  • Steeper learning curve for developers new to React or server-side rendering
  • More opinionated framework, which may limit flexibility in some cases

Code Comparison

Next.js:

// pages/index.js
export default function Home() {
  return <h1>Welcome to Next.js!</h1>
}

After.js:

// src/Home.js
import { asyncComponent } from '@jaredpalmer/after'

export default asyncComponent({
  async getInitialProps() {
    return { message: 'Welcome to After.js!' }
  },
  render: ({ message }) => <h1>{message}</h1>,
})

Summary

Next.js offers a more comprehensive solution with a larger ecosystem and built-in optimizations, making it suitable for large-scale projects. After.js provides a simpler, more lightweight approach that may be preferable for smaller projects or developers who want more control over their setup. Both frameworks support server-side rendering and code splitting, but Next.js has more advanced features out of the box. The code comparison shows that Next.js has a simpler component structure, while After.js requires more boilerplate for async data fetching.

31,388

Build Better Websites. Create modern, resilient user experiences with web fundamentals.

Pros of Remix

  • Built-in server-side rendering and data loading, simplifying the development of full-stack applications
  • Nested routing system that allows for more intuitive and efficient route management
  • Automatic code splitting and asset preloading for improved performance

Cons of Remix

  • Steeper learning curve due to its unique approach to data loading and routing
  • Less flexibility in choosing backend technologies, as it's more tightly integrated with its own server-side solutions

Code Comparison

Remix route example:

export async function loader({ params }) {
  const user = await getUser(params.id);
  return json({ user });
}

export default function UserProfile() {
  const { user } = useLoaderData();
  return <h1>{user.name}</h1>;
}

After.js route example:

import { withRouter } from 'after';

class UserProfile extends React.Component {
  static async getInitialProps({ req, res, match }) {
    const user = await getUser(match.params.id);
    return { user };
  }
  render() {
    return <h1>{this.props.user.name}</h1>;
  }
}

export default withRouter(UserProfile);

Both frameworks aim to simplify server-side rendering and data fetching in React applications. Remix offers a more integrated approach with its own routing and data loading systems, while After.js provides a solution more closely aligned with Next.js conventions. The choice between them depends on project requirements and developer preferences.

55,893

The best React-based framework with performance, scalability and security built in.

Pros of Gatsby

  • Extensive plugin ecosystem for easy integration of various features and data sources
  • Strong focus on performance optimization, including automatic code splitting and image optimization
  • Large and active community, providing extensive resources and support

Cons of Gatsby

  • Steeper learning curve, especially for developers new to GraphQL
  • Can be overkill for smaller projects or simple static sites
  • Build times can become slow for large sites with many pages

Code Comparison

After.js:

import { withRouter } from 'after'

const Home = ({ router }) => (
  <div>Welcome to {router.query.name}!</div>
)

export default withRouter(Home)

Gatsby:

import { graphql } from 'gatsby'

const Home = ({ data }) => (
  <div>Welcome to {data.site.siteMetadata.title}!</div>
)

export const query = graphql`
  query HomeQuery {
    site {
      siteMetadata {
        title
      }
    }
  }
`

export default Home

The code comparison shows that After.js uses a more straightforward approach with React Router, while Gatsby leverages GraphQL for data querying. Gatsby's approach allows for more complex data management but requires additional setup and understanding of GraphQL.

19,439

web development, streamlined

Pros of SvelteKit

  • Built-in routing and code-splitting for optimal performance
  • Seamless server-side rendering (SSR) and static site generation (SSG)
  • Leverages Svelte's reactive and efficient compilation approach

Cons of SvelteKit

  • Smaller ecosystem compared to React-based solutions
  • Steeper learning curve for developers new to Svelte
  • Less flexibility in choosing routing or state management libraries

Code Comparison

After.js (React-based):

import { withRouter } from 'after'

class Page extends React.Component {
  static async getInitialProps({ req, res, match, history, location, ...ctx }) {
    const data = await fetchData()
    return { data }
  }
  render() {
    return <div>{this.props.data}</div>
  }
}

export default withRouter(Page)

SvelteKit:

<script context="module">
  export async function load({ page, fetch, session, context }) {
    const data = await fetchData()
    return { props: { data } }
  }
</script>

<script>
  export let data
</script>

<div>{data}</div>

Both frameworks offer server-side rendering and routing capabilities, but SvelteKit provides a more streamlined approach with less boilerplate. After.js relies on React's ecosystem, while SvelteKit leverages Svelte's efficient compilation and simpler syntax.

57,427

The Intuitive Vue Framework.

Pros of Nuxt

  • More comprehensive framework with built-in features like automatic routing, server-side rendering, and static site generation
  • Larger ecosystem and community support, with numerous plugins and modules available
  • Better documentation and learning resources

Cons of Nuxt

  • Steeper learning curve due to its more opinionated structure and conventions
  • Potentially heavier bundle size for smaller projects
  • Less flexibility in customizing the build process compared to After.js

Code Comparison

After.js route definition:

export default {
  async getInitialProps({ req, res, match }) {
    const data = await fetchData(match.params.id);
    return { data };
  },
  render({ data }) {
    return <div>{data.title}</div>;
  }
};

Nuxt page component:

<template>
  <div>{{ data.title }}</div>
</template>

<script>
export default {
  async asyncData({ params }) {
    const data = await fetchData(params.id);
    return { data };
  }
};
</script>

Both frameworks offer server-side rendering and code-splitting capabilities, but Nuxt provides a more structured approach with its file-based routing system and built-in async data fetching. After.js offers more flexibility and is closer to vanilla React, making it easier to integrate into existing projects. The choice between the two depends on project requirements, team expertise, and desired level of abstraction.

17,637

RedwoodGraphQL

Pros of GraphQL

  • Integrated GraphQL support with built-in schema generation and API handling
  • Full-stack framework approach, providing a more comprehensive solution
  • Strong focus on developer experience and productivity

Cons of GraphQL

  • Steeper learning curve due to its full-stack nature and custom conventions
  • Less flexibility for custom server-side rendering configurations
  • More opinionated structure, which may not suit all project types

Code Comparison

After.js (server-side rendering):

import { render } from '@jaredpalmer/after';
import routes from './routes';

const html = await render({
  req,
  res,
  routes,
  assets,
});

RedwoodJS GraphQL (API definition):

export const schema = gql`
  type Query {
    users: [User!]!
  }
`;

export const resolver = {
  Query: {
    users: () => db.user.findMany(),
  },
};

While After.js focuses on server-side rendering React applications, RedwoodJS GraphQL provides a more comprehensive full-stack solution with integrated GraphQL support. After.js offers more flexibility for custom SSR setups, while RedwoodJS GraphQL emphasizes developer productivity and a standardized approach to building full-stack applications.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

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

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 fallback
    component: Notfound,
  },
];

Notfound component must set staticContext.statusCode to 404 so express can set response status code more info.

// ./src/Notfound.js

import React from 'react';
import { Route } from 'react-router-dom';

function NotFound() {
  return (
    <Route
      render={({ staticContext }) => {
        if (staticContext) staticContext.statusCode = 404;
        return <div>The Page You Were Looking For Was Not Found</div>;
      }}
    />
  );
}

export default NotFound;

if you don't declare 404 component in routes.js After.js will use its default fallback.

Dynamic 404

Sometimes you may need to send a 404 response based on some API response, in this case, react-router don't show fallback and you have to check for that in your component.

import Notfound from './Notfound';

function ProductPage({ product, error }) {
  if (error) {
    if (error.response.status === 404) {
      return <Notfound />;
    }

    return <p>Something went Wrong !</p>;
  }
  {
    /* if there were no errors we have our data */
  }
  return <h1>{product.name}</h1>;
}

ProductPage.getInitialProps = async ({ match }) => {
  try {
    const { data } = await fetchProduct(match.params.slug);
    return { product: data };
  } catch (error) {
    return { error };
  }
};

this makes code unreadable and hard to maintain. after.js makes this easy by providing an API for handling Dynamic 404 pages. you can return { statusCode: 404 } from getInitialProps and after.js will show 404 fallback components that you defined in routes.js for you.

function ProductPage({ product }) {
  return <h1>{product.name}</h1>;
}

ProductPage.getInitialProps = async ({ match }) => {
  try {
    const { data } = await fetchProduct(match.params.slug);
    return { product: data };
  } catch (error) {
    if (error.response.status === 404) return { statusCode: 404 };
    return { error };
  }
};

Redirect

You can redirect the user to another route by using Redirect from react-router, but it can make your code unreadable and hard to maintain. with after.js you can redirect client to other route by returning { redirectTo: "/new-location" } from getInitialProps. this can become handy for authorization when user does not have permission to access a specific route and you can redirect him/her to the login page.

Dashboard.getInitialProps = async ({ match }) => {
  try {
    const { data } = await fetchProfile();
    return { data };
  } catch (error) {
    if (error.response.status === 401) return { redirectTo: '/login' };
    return { error };
  }
};

The redirect will happen before after.js start renders react to string soo it's fast. when using redirectTo default value for statusCode is 301, but you can use any numeric value you want.

Code Splitting

After.js lets you easily define lazy-loaded or code-split routes in your _routes.js file. To do this, you'll need to modify the relevant route's component definition like so:

// ./src/_routes.js
import React from 'react';
import Home from './Home';
import { asyncComponent } from '@jaredpalmer/after';

export default [
  // normal route
  {
    path: '/',
    exact: true,
    component: Home,
  },
  // codesplit route
  {
    path: '/about',
    exact: true,
    component: asyncComponent({
      loader: () => import('./About'), // required
      Placeholder: () => <div>...LOADING...</div>, // this is optional, just returns null by default
    }),
  },
];

Static Site Generation (SSG)

After.js has first class support for SSG and allows you to create super fast static webapps and serve them over CDN.

renderStatic will return the data from getInitialProps and this data will get saved by razzle into a file called page-data.json. After.js won't call getInitialProps at runtime, instead it will read the page-data.json and pass it as a prop to your component.

from ./src/static_export.js you should export render and routes function.

  • async render(req, res) should render your app into html and at the end it should return html and data.
  • async routes() should return path for pages you want to statically generate.
// ./src/static_export.js

import { renderStatic } from '@jaredpalmer/after';
import appRoutes from './routes';

const assets = require(process.env.RAZZLE_ASSETS_MANIFEST);
const chunks = require(process.env.RAZZLE_CHUNKS_MANIFEST);

export const render = async (req, res) => {
  const { html, data } = await renderStatic({
    req,
    res,
    routes: appRoutes,
    assets,
    chunks,
  });
  res.json({ html, data });
};

export const routes = async () => {
  return ['/', '/about'];
};

after setting up this file you can build your app and run export script to generate your static site:

yarn build
yarn export

for full documentation and advanced configuration visit: https://razzlejs.org/docs/static-export

Disable Auto-Scroll Globally

By default, After.js will scroll to top when URL changes, you can change that by passing scrollToTop: false to render().

// ./src/server.js

const scrollToTop = false;

const html = await render({
  req,
  res,
  routes,
  chunks,
  scrollToTop,
});

Disable Auto-Scroll for a Specific Page

We are using a ref object to minimize unnecessary re-renders, you can mutate scrollToTop.current and component will not re-rendered but its scroll behavior will change immediately. You can control auto-scroll behavior from getInitialProps.

class MyComponent extends React.Component {
  static async getInitialProps({ scrollToTop }) {
    scrollToTop.current = false;
    return { scrollToTop, stuff: 'whatevs' };
  }

  render() {
    return <h1>Hello, World!</h1>;
  }

  componentWillUnmount() {
    this.props.scrollToTop.current = true; // at the end restore scroll behavior
  }
}

Custom <Document>

After.js works similarly to Next.js with respect to overriding HTML document structure. This comes in handy if you are using a CSS-in-JS library or just want to collect data out of react context before or after render. To do this, create a file in ./src/Document.js like so:

// ./src/Document.js
import React from 'react';
import {
  AfterRoot,
  AfterData,
  AfterScripts,
  AfterStyles,
} from '@jaredpalmer/after';

class Document extends React.Component {
  static async getInitialProps({ renderPage }) {
    const page = await renderPage();
    return { ...page };
  }

  render() {
    const { helmet } = this.props;
    // get attributes from React Helmet
    const htmlAttrs = helmet.htmlAttributes.toComponent();
    const bodyAttrs = helmet.bodyAttributes.toComponent();

    return (
      <html {...htmlAttrs}>
        <head>
          <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
          <meta charSet="utf-8" />
          <title>Welcome to the Afterparty</title>
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {helmet.title.toComponent()}
          {helmet.meta.toComponent()}
          {helmet.link.toComponent()}
          <AfterStyles />
        </head>
        <body {...bodyAttrs}>
          <AfterRoot />
          <AfterData />
          <AfterScripts />
        </body>
      </html>
    );
  }
}

export default Document;

If you were using something like styled-components, and you need to wrap you entire app with some sort of additional provider or function, you can do this with renderPage().

// ./src/Document.js
import React from 'react';
import { ServerStyleSheet } from 'styled-components';
import { AfterRoot, AfterData, AfterScripts } from '@jaredpalmer/after';

export default class Document extends React.Component {
  static async getInitialProps({ renderPage }) {
    const sheet = new ServerStyleSheet();
    const page = await renderPage(App => props =>
      sheet.collectStyles(<App {...props} />)
    );
    const styleTags = sheet.getStyleElement();
    return { ...page, styleTags };
  }

  render() {
    const { helmet, styleTags } = this.props;
    // get attributes from React Helmet
    const htmlAttrs = helmet.htmlAttributes.toComponent();
    const bodyAttrs = helmet.bodyAttributes.toComponent();

    return (
      <html {...htmlAttrs}>
        <head>
          <meta charSet="utf-8" />
          <meta name="viewport" content="width=device-width, initial-scale=1" />
          {helmet.title.toComponent()}
          {helmet.meta.toComponent()}
          {helmet.link.toComponent()}
          {/* here is where we put our Styled Components styleTags... */}
          {styleTags}
        </head>
        <body {...bodyAttrs}>
          <AfterRoot />
          <AfterData />
          <AfterScripts />
        </body>
      </html>
    );
  }
}

To use your custom <Document>, pass it to the Document option of your After.js render function.

// ./src/server.js
import express from 'express';
import { render } from '@jaredpalmer/after';
import routes from './routes';
import MyDocument from './Document';

const chunks = require(process.env.RAZZLE_CHUNKS_MANIFEST);

const server = express();
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    try {
      // Pass document in here.
      const html = await render({
        req,
        res,
        document: MyDocument,
        chunks,
        routes,
      });
      res.send(html);
    } catch (error) {
      console.error(error);
      res.json({ message: error.message, stack: error.stack });
    }
  });

export default server;

Custom/Async Rendering

You can provide a custom (potentially async) rendering function as an option to After.js render function.

If present, it will be used instead of the default ReactDOMServer renderToString function.

It has to return an object of shape { html : string!, ...otherProps }, in which html will be used as the rendered string

Thus, setting customRenderer = (node) => ({ html: ReactDOMServer.renderToString(node) }) is the the same as default option.

otherProps will be passed as props to the rendered Document

Example :

// ./src/server.js
import React from 'react';
import express from 'express';
import { render } from '@jaredpalmer/after';
import { renderToString } from 'react-dom/server';
import { ApolloProvider, getDataFromTree } from 'react-apollo';
import routes from './routes';
import createApolloClient from './createApolloClient';
import Document from './Document';

const chunks = require(process.env.RAZZLE_CHUNKS_MANIFEST);

const server = express();
server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', async (req, res) => {
    const client = createApolloClient({ ssrMode: true });

    const customRenderer = node => {
      const App = <ApolloProvider client={client}>{node}</ApolloProvider>;
      return getDataFromTree(App).then(() => {
        const initialApolloState = client.extract();
        const html = renderToString(App);
        return { html, initialApolloState };
      });
    };

    try {
      const html = await render({
        req,
        res,
        routes,
        chunks,
        customRenderer,
        document: Document,
      });
      res.send(html);
    } catch (error) {
      console.error(error);
      res.json({ message: error.message, stack: error.stack });
    }
  });

export default server;

Author

Inspiration


MIT License

NPM DownloadsLast 30 Days