Giter VIP home page Giter VIP logo

after.js's Introduction

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

after.js's People

Contributors

brunorzn avatar dagda1 avatar datenpate avatar dependabot[bot] avatar dimensi avatar fredrik-sogaard avatar iineva avatar imgbot[bot] avatar isbasex avatar jaredpalmer avatar jee1mr avatar kasbah avatar kgoggin avatar lednhatkhanh avatar madumo avatar march08 avatar mohsen1 avatar mrmartineau avatar nicholasess avatar nimaa77 avatar nolandg avatar prichodko avatar rclmenezes avatar simondegraeve avatar smkhalsa avatar snyk-bot avatar stepanh avatar tylermcginnis avatar wokayme avatar zinoviev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

after.js's Issues

Module not found: Can't resolve 'react-dom/unstable-native-dependencies'

Hi ! I tried to use react-native-web in after.js but I've got this error :

Module not found: Can't resolve 'react-dom/unstable-native-dependencies'

The error is located in a file from react-native-web but It doesn't seem to be directly link to this project.

I deleted .babelrc from my after.js project, delete all imports of react-native-web. Then adding in a component :

import ReactDOMUnstableNativeDependencies from 'react-dom/unstable-native-dependencies'

And it produce the same error.

I also tried in a next.js project and it doesn't produce this error.

node v9.1.0
yarn 1.3.2
react & react-dom 16.2.0
after 0.5.1

Overridable Document

Allow users to override a custom _document.js that would be rendered to string on the server.

Psuedo code...

import React from 'react';
import Helmet from 'react-helmet' 

class Document extends React.Component {
   static getInitialProps() {
    // call this before calling ReactDOMServer.renderToString   
    return { stuff: 'like css' }
   }
   
    render() {
      const { stuff } = this.props
      return (
       <html>
         <head>
             
         </head>
         <body>
           
         </body>
       </html>
       );
   }

}

export default Document;

On code change recompiling error

Hi, I get this error after any code edit. I've started with basic "hello world" app:

  • Start with blank project, add dependencies
  • Add single route, single component.
  • Start server, compiling is fine
  • Edit any file, recompiling fails

Mac OS - 10.13.1, node version - 8.2.1

`Your application is running at http://localhost:3000

Started server on port 3000
/Users/zigi/Temp/after.js/node_modules/@jaredpalmer/after/scripts/start.js:100
fs.copyFile(changedPath, tempSrc.replace('src', changedPath), err => {
^

TypeError: fs.copyFile is not a function
at FSWatcher.chokidar.watch.on.changedPath (/Users/zigi/Temp/after.js/node_modules/@jaredpalmer/after/scripts/start.js:100:10)
at emitOne (events.js:115:13)
at FSWatcher.emit (events.js:210:7)
at FSWatcher. (/Users/zigi/Temp/after.js/node_modules/chokidar/index.js:198:15)
at FSWatcher._emit (/Users/zigi/Temp/after.js/node_modules/chokidar/index.js:240:5)
at FSWatcher. (/Users/zigi/Temp/after.js/node_modules/chokidar/lib/fsevents-handler.js:206:14)
at addOrChange (/Users/zigi/Temp/after.js/node_modules/chokidar/lib/fsevents-handler.js:212:7)
at FSWatcher. (/Users/zigi/Temp/after.js/node_modules/chokidar/lib/fsevents-handler.js:238:16)
at filteredListener (/Users/zigi/Temp/after.js/node_modules/chokidar/lib/fsevents-handler.js:60:7)
at /Users/zigi/Temp/after.js/node_modules/chokidar/lib/fsevents-handler.js:85:11
at Array.forEach (native)
at FSEvents. (/Users/zigi/Temp/after.js/node_modules/chokidar/lib/fsevents-handler.js:84:34)
at emitThree (events.js:135:13)
at FSEvents.emit (events.js:216:7)
at Immediate._onImmediate (/Users/zigi/Temp/after.js/node_modules/fsevents/fsevents.js:47:11)
at runCallback (timers.js:781:20)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] start: after start
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR! /Users/zigi/.npm/_logs/2018-01-22T09_44_57_431Z-debug.log`

package.json:
{ "name": "after", "version": "1.0.0", "description": "", "scripts": { "start": "after start", "test": "after test --env=jsdom", "build": "after build", "start:prod": "NODE_ENV=production node build/build/server.js" }, "author": "", "license": "ISC", "dependencies": { "@jaredpalmer/after": "^0.5.2", "react": "^16.2.0", "react-dom": "^16.2.0", "react-helmet": "^5.2.0", "react-router-dom": "^4.2.2" } }

Client-side subrouting with top level component (Navigation for example).

I've tried creating client-side subroutes like in a fully client-side rendered application with RR4 but After.js seems to be ignorant about any components not defined in _routes.js since only telling React about the components causes After to not even download them and those route pages just display blank. It seems that server-side pre-rendered routing triggers a complete wipe of the root content. My goal with this is to have control over page transition animations and not lose client-side state. If there's a simple solution to having control over partially updating the page based on client-side routes(simplest use case would be for navigation). Another question that falls into this category is: can I have my NavLinks defined in only one component that simply doesn't get wiped on route-change?

I'd be really happy if someone could point me in the direction that I'm failing to see. (basically looking for easy alternative to Next.js regarding this problem)

Integration with the redux

It is desirable to have "out of the box" integration with the redux. So far I can get it all from the next.js and can not with the after.js:
"next-redux-wrapper": redux integration.
"next-routes": after.js's-like routes.

So. I tried to change ./src/_document.js, but it was replaced with system default ./src/_document.js on server start.

take the routes file as a param instead of hardcoded?

Just wondering... it seems like from reading the docs that After always takes a src/_routes.js file as input, and doesn't care about the rest of the directory structure other than that? If so, it seems like it would be awesome if it actually took that file's path as an input similar to other bundlers, instead of enforcing any directory structure at all, like:

after ./src/_routes.js

Or, up to the user at the point...

after ./src/routes.js
after ./browser/index.js
after ./routes.js
...

I might have missed other reasons that After enforces a specific directory structure though in my quick reading, so feel free to close if so.

getInitialProps not fetching on page refresh

Great project! 👍

Some questions:
Seems like static async getInitialProps only gets called when using a Link to access the route.

For example If you refresh on the /about route, you will see the page gets stuck on "Loading..." and nothing is ssr'd when you view source:
https://after-examples-basic-wycoflvnsg.now.sh/

Am I misunderstanding the framework? Should data returned from getInitialProps get server side rendered? The docs lead me to believe yes, but this isnt working as expected:

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

class About extends React.Component {
  static async getInitialProps({ req, res, match }) {
    const hn = await fetch('https://node-hnapi.herokuapp.com/news');
    const stuff = await hn.json();
    return { stuff };
  }

  render() {
    return (
      <div>
        <NavLink to="/">Home</NavLink>
        <NavLink to="/about">About</NavLink>
        <h1>About</h1>
        <pre>
          {this.props.stuff
            ? this.props.stuff.map(item => <p key={item.id}>{item.title}</p>)
            : 'Loading...'}
        </pre>
      </div>
    );
  }
}

export default About;

Any info appreciated, thanks! And keep up the good work 👍

Discussion: Render Blocking Flag

Although I prefer to set state.data to undefined during page changes (which allows for an easy way to writing a loading indicator), some users may want to keep existing state present. We could potentially accomplish this with a flag.

Thoughts?

Objection.js models don't work

I have a project on Razzle with existing working code.

I created a new project, following the Readme. Once I got data fetching working, I started to copy over my old project files.
There were a few gotchas along the way, but got those worked out as well.

Then i began testing the APIs once I stopped getting server compiling errors.

All my knex related calls went through. The ones with Object.js Models
all result in

err TypeError: CoinModel.query is not a function
    at Coins._callee$ (/home/web/build/build/server.js:2082:34)
    at tryCatch (/home/web/node_modules/regenerator-runtime/runtime.js:62:40)
    at Generator.invoke [as _invoke] (/home/web/node_modules/regenerator-runtime/runtime.js:296:22)
    at Generator.prototype.(anonymous function) [as next] (/home/web/node_modules/regenerator-runtime/runtime.js:114:21)
    at step (/home/web/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
    at /home/web/node_modules/babel-runtime/helpers/asyncToGenerator.js:35:14
    at new Promise (<anonymous>)
    at new F (/home/web/node_modules/core-js/library/modules/_export.js:35:28)
    at Coins.<anonymous> (/home/web/node_modules/babel-runtime/helpers/asyncToGenerator.js:14:12)
    at Coins.test (/home/web/build/build/server.js:2097:21)
    at /home/web/build/build/server.js:3815:17
    at Layer.handle [as handle_request] (/home/web/node_modules/express/lib/router/layer.js:95:5)
    at next (/home/web/node_modules/express/lib/router/route.js:137:13)
    at Route.dispatch (/home/web/node_modules/express/lib/router/route.js:112:3)
    at Layer.handle [as handle_request] (/home/web/node_modules/express/lib/router/layer.js:95:5)
    at /home/web/node_modules/express/lib/router/index.js:281:22

I'm going to try swapping out for Bookshelf and seeing if that fixes the problem, but Like I said, it was working code on razzle.

[Help Wanted]: Prefetching / caching

Right now we pass down prefetch: (pathname: string) => void as a prop to each page. It keeps a cache of the result in _app.js's this.prefetcherCache. There is probably a nicer way and more performant way to keep this data around. Please chime in here.

Regarding cache invalidation, wondering if we should have a second argument that is either:

  • number of milliseconds to keep the result
  • a function used to determine whether or not to use the cached data
  • something else

Thoughts?

"Expected server HTML to contain..." warning in console

I appreciate this probably isn't an issue with after.js but I can't find a definitive way to resolve it: every once in a while, I get this kind of error in console -

Warning: Expected server HTML to contain a matching text node for "Viewport height: " in <p>.

Once I restart after, it'll work correctly, but then eventually appear again. Where am I going wrong here?

Airbnb eslint error

Right now I can't use airbnb eslint style because it's conflicting with the built-in eslint version, can we have an option to remove eslint entirely and install our own eslint version?

Overridable built-in components

After.js should allow users to define their own versions of the built-in files:

  • _client.js - Client side mount
  • _document.js - HTML static document container
  • _app.js??? - Route iteration (maybe)

Layout pages

Nice work, this looks really promising.

I have a small question, is there a way to define a layout, to reuse a component on each page (e.g., a navigation component) ?

Static Export

This should be fairly straight forward and quite pleasant actually. This will be like Gatsby, but minus the GraphQL.

Should we be transpiling lib?

Everything is copied into from lib to ./build/src so it can theoretically be overridden in user land. However, I'm pretty sure that publishing any untranspiled code to npm is generally considered a bad idea. On the other hand, we can 100% guarantee that this is going to be transpiled. So idk. Thoughts?

cc @gaearon

Question: How to extend config and add post-css plugins?

How would I go about extending the config and adding a few simple postcss plugins? I've got it working by modifying createConfig.js directly like so:

{
  loader: require.resolve('postcss-loader'),
  options: {
    ident: 'postcss', // https://webpack.js.org/guides/migrating/#complex-options
    plugins: () => [
      require('postcss-easy-import'),
      require('postcss-flexbugs-fixes'),
      require('precss'),
      autoprefixer({
        browsers: [
          '>1%',
          'last 4 versions',
          'Firefox ESR',
          'not ie < 9', // React doesn't support IE8 anyway
        ],
        flexbox: 'no-2009',
      }),
    ],
  },
}

Is there a simple way of doing this in after.config.js?

Nested Routing

How could the behavior in _routes.js be done in smaller sections of the application? I like the idea of a single top level router mechanism, but how would you implement routing inside one of the pages (like '/about', '/about/foo', '/about/bar') without duplicating the code and adding them as top level routes?

PS: I was curios to test this out with a Layout component so I can do an example PR maybe it makes more sense to talk about that one.
PS2: Awesome work, I love this idea

Overriding or extending server config

Is there a way to create your own server config (Changing ports, using custom routes, enable specific middleware, etc.) ? I searched but couldnt find anything in the documentation or issues.

Custom Express.js server

Hello,
How can we add a custom Express.js server like in Next.js ?

const app = nextjs({dev})
app
.prepare()
.then(() => {
const server = express()
...

introducing env-hoc lib / feature request

Made a library for easier working with cookies / supported languages / userAgent / ipAddress in Next.js (works also with After.js). Check it out: env-hoc.

While I was writing the library I thought about the idea of having a specification for getInitialProps, would make it easier to write libraries and ensure that in the near future there won't be a dozen of frameworks with there own getInitialProps implementation. The most important thing would be to ensure the arguments passed to getInitialProps are the same and that they support Promises / async. What's your opinion? And what do you think about implementing an additional prop to the getInitialProps object named serverProps, which gives access to the result of server-rendered getInitialProps instead of accessing the props somewhat hacky through window.__NEXT_DATA__.props. For libraries, there isn't really any other solution and the props are so-or-so available.

Would also like to hear your opinion. vercel/next.js#3625

npm start/build crashes

My package.json (using npm init)


{
  "name": "clinical-web-after",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "start": "after start",
    "test": "after test --env=jsdom",
    "build": "after build",
    "start:prod": "NODE_ENV=production node build/build/server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@jaredpalmer/after": "^0.5.0",
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-helmet": "^5.2.0",
    "react-router-dom": "^4.2.2"
  }
}

And the error when running npm start

FMGordillo-MacBookPro:clinical-web-after facundogordillo$ npm start

> [email protected] start /Users/facundogordillo/Documents/Projects/clinical-web-after
> after start

/Users/facundogordillo/Documents/Projects/clinical-web-after/node_modules/@jaredpalmer/after/scripts/start.js:65
async function start() {
      ^^^^^^^^
SyntaxError: Unexpected token function
    at createScript (vm.js:56:10)
    at Object.runInThisContext (vm.js:97:10)
    at Module._compile (module.js:542:28)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    ...

It also happens with npm run build


FMGordillo-MacBookPro:clinical-web-after facundogordillo$ npm run build

> [email protected] build /Users/facundogordillo/Documents/Projects/clinical-web-after
> after build

/Users/facundogordillo/Documents/Projects/clinical-web-after/node_modules/@jaredpalmer/after/scripts/build.js:73
async function build(previousFileSizes) {
      ^^^^^^^^
SyntaxError: Unexpected token function
    at createScript (vm.js:56:10)
    at Object.runInThisContext (vm.js:97:10)
    at Module._compile (module.js:542:28)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    ...

Most probably my mistake, but I want to double check

Database connection

It would be a problem if I make database connection instead of using CallMyAPI pattern ?

fs.copyfile is not a function

node_modules/@jaredpalmer/after/scripts/start.js:100
      fs.copyFile(changedPath, tempSrc.replace('src', changedPath), err => {
         ^

TypeError: fs.copyFile is not a function
    at FSWatcher.chokidar.watch.on.changedPath (/home/paul/web/-cart/node_modules/@jaredpalmer/after/scripts/start.js:100:10)
    at emitTwo (events.js:125:13)
    at FSWatcher.emit (events.js:213:7)
    at FSWatcher.<anonymous> (/home/paul/web/-cart/node_modules/chokidar/index.js:198:15)
    at FSWatcher._emit (/home/paul/web/-cart/node_modules/chokidar/index.js:240:5)
    at FSWatcher.<anonymous> (/home/paul/web/-cart/node_modules/chokidar/lib/nodefs-handler.js:263:16)
    at FSReqWrap.oncomplete (fs.js:153:5)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] start: `after start`

I get this error after following the guide and saving a change in either Home or About.

also tried erasing everything and starting from scratch.

node v8.4.0
npm v5.6

Router Plugins

We could push plugins in a few ways:

  • Gatsby-style: have users specify plugins in after.config.js. What's nice is that these could be webpack OR router plugins.
  • Move _routes.js to use react-router-config and allow the user to specify their own Root component.
  • Something else

Basically, we need to give use the ability to have code execute in a few places:

  • before / after navigation (in _app.js's componentWillReceiveProps or equivalent)
  • before render on the server

@mjackson Would love your input on this.

Create a contributing guide

Hello! first, congratulations for that amazing project !

I'm thinking that's a good way to help new contributors to understand the architecture and patterns of the project, before start code something...

What do you think about it? and how I could help you with that ?

Thanks!

Sass webpack extension breaks typescript example

Beginner here and I'm trying to integrate sass-loader into the with-typescript example. Unfortunately, it seems to break the project. Appending the following:

// after.config.js

...
// Locate css loader and remove it
config.module.rules.filter(rule => !(rule.test && String(rule.test) === String(/\.css$/)));

// Declare scss loader configuration
config.module.rules.push({
    test: /\.(sass|scss)$/,
    use: ['style-loader', 'css-loader', 'sass-loader']
});
...

results in:

 DONE  Compiled successfully

Your application is running at http://localhost:3000

<project-path>/with-typescript/build/build/server.js:1473
	return window && document && document.all && !window.atob;
	^

ReferenceError: window is not defined
    at <project-path>/with-typescript/build/build/server.js:1473:2
    at <project-path>/with-typescript/build/build/server.js:1462:46
    at __dirname../node_modules/style-loader/lib/addStyles.js.module.exports (<project-path>/with-typescript/build/build/server.js:1507:46)
    at Object../build/src/scss/bootstrap.scss (<project-path>/with-typescript/build/build/server.js:1189:81)
    at __webpack_require__ (<project-path>/with-typescript/build/build/server.js:630:30)
    at fn (<project-path>/with-typescript/build/build/server.js:48:20)
    at Object../build/src/Home.tsx (<project-path>/with-typescript/build/build/server.js:744:79)
    at __webpack_require__ (<project-path>/with-typescript/build/build/server.js:630:30)
    at fn (<project-path>/with-typescript/build/build/server.js:48:20)
    at Object../build/src/_routes.tsx (<project-path>/with-typescript/build/build/server.js:1080:64)

Unfortunately, I couldn't find any examples for after.js directly and the ones for razzle didn't help - they have the approach of only including the sass loader on target === "web" and ignoring for the node target. Then the setup compiles properly, however the dev and production build appear to load the scss multiple times. My scss file only contains body { background-color: purple; }. On page load, the background is purple, changes back to white, then back to purple again. Best described as flickering.

I feel like I'm missing something fundamental here and would appreciate if someone could shed some light into this. Thanks in advance!

Got error on installing package

Hi @jaredpalmer ,

I love this library so much by reading the philosophy / project goals, I was wondering, it would be amazing if next.js had react-router v4 as its routing system rather than dealing with project structure. now this is really happening.

I got a little problem on installing this package.
I use this script as in readme

npm i @jaredpalmer/after react-router-dom react-dom --save

then I got this error

npm ERR! Windows_NT 10.0.10586
npm ERR! argv "C:\Program Files\nodejs\node.exe" "C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js" "i" "@jaredpalmer/after" "react-router-dom" "react-dom" "--save"
npm ERR! node v7.6.0
npm ERR! npm v4.1.2
npm ERR! code E404
npm ERR! 404 Not found : jaredpalmer/after
npm ERR! 404
npm ERR! 404 jaredpalmer/after is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
npm ERR! Please include the following file with any support request:
npm ERR! D:\EXPERIMENT\REACT\after-app\npm-debug.log

Could you please help me?

Thank you

Component rendering twice, once without initialProps

You can replicate with the GraphQL example by loading / and navigating to /posts on the client.

Order of occurrences:
Initial props are fetched
static getInitialProps() on WithData is called
render WithData (without props)
ERROR required prop missing
render WithData (with props)

This leads me to believe it's an issue with _app.js and/or routing

AirBnB linting example fails to build

Hi,

I've copied over the .eslintrc and all dependencies from the example into my project and I get an error as soon as I restart:

Using .eslintrc defined in your app root
Using .eslintrc defined in your app root
 ERROR  Failed to compile SERVER with 7 errors

./build/src/index.js
  Line 9:  Expected parentheses around arrow function argument having a body with curly braces  arrow-parens

Search for the keywords to learn more about each error.

 ERROR  Failed to compile SERVER with 7 errors

./build/src/server.js
  Line 5:   Absolute imports should come before relative imports                       import/first
  Line 6:   Absolute imports should come before relative imports                       import/first
....

I won't include the full error as it does it for a number of components.

The .eslintrc I'm using is the same as in the example:

{
  "parser": "babel-eslint",
  "extends": "airbnb",
  "env": {
    "es6": true,
    "browser": true,
    "jest": true,
    "node": true
  },
  "rules": {
    "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }]
  }
}

As soon as I delete the file all works as standard.

Deps

deps:
"@jaredpalmer/after": "^0.5.2",

devDeps:
"eslint": "^4.16.0",
 "eslint-config-airbnb": "^16.1.0",
    "eslint-plugin-import": "^2.8.0",
    "eslint-plugin-jsx-a11y": "^6.0.3",
    "eslint-plugin-react": "^7.5.1",

Improve contributor tooling

Lots of restarts are required, need to add a watch command and make gulp tasks more forgiving to syntax errors.

How push to cloud (using now.sh)

I struggled a lot to make this work, but I get issues regarding 'build' script.

Using only the example:

> ...
> Using Node.js 8.9.4 (requested: `8.x.x`)
> Ready! ... [10s]
> Synced 13 files (286.97KB) [0ms]
> Initializing…
> Building
> ▲ npm install
> ✓ Using "package-lock.json"
> ⧗ Installing 5 main dependencies…
> ✓ Installed 1465 modules [56s]
> ▲ npm run build
> > [email protected] build /home/nowuser/src
> > after build
> Failed to compile.
> ENOENT: no such file or directory, stat '/home/nowuser/src/public'
> npm ERR! code ELIFECYCLE
> npm ERR! errno 1
> npm ERR! [email protected] build: `after build`
> npm ERR! Exit status 1
> npm ERR!
> npm ERR! Failed at the [email protected] build script.
> npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
> npm ERR! A complete log of this run can be found in:
> npm ERR!     /home/nowuser/.npm/_logs/2018-01-17T03_42_56_496Z-debug.log
> Error! The build step of your project failed. To retry, run `now --force`.

I've tried adding and excluding folder '/build' in .gitignore but the results are very different with each deploy. Any advice?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.