Arunoda Susiripala, de MeteorJS à Next.js

Disclaimer !

MeteorJS

Brièvement

Réconcilier client/serveur

Déploiement

~ meteor deploy your-app.com --settings production-settings.json

Contributions

  • Meteor-up
  • Flow-router 
  • Meteor hacks (SSR ...)
  • ...

Le 30/12/2016 ...

Kadira is a 100 per cent bootstrapped company from my personal funds.

[...]

We were pretty successful with Meteor, but the market of Meteor apps was pretty small for us.

Universal JS Application

That situation was really no better than, say, PHP's. In many ways, PHP was actually more suited for the "server rendering of HTML" job

NEXTJs

  • ReactJS
  • SSR
  • Automatic code splitting
  • Zero setup

Installation

~ yarn create next-app my-app

~ cd my-app

~ yarn next

Yarn > 0.25

~ npm install -g create-next-app

~ create-next-app my-app

~ cd my-app

~ npm run dev

NPM

pages/index.js

pages/rennes.js

export default () => <h1>What a new Page !</h1>;

A partir d'une url, seul le code nécessaire est chargé

Configuration des Metas

import Head from 'next/head';

export default ({ title = 'This is the default title' }) => (
  <Head>
    <title>{title}</title>
    <meta charSet="utf-8" />
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
  </Head>
);

Query Param

export default ({ url: { query: { id } } }) => (
  <div>
    <h1>Mon identifiant est : {id}</h1>
  </div>
);

/sub/path

Path Param

{
  "name": "create-next-example-app",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "^2.3.1",
    "react": "^15.5.4",
    "react-dom": "^15.5.4"
  }
}
{
  "name": "create-next-example-app",
  "scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  },
  "dependencies": {
    "next": "^2.3.1",
    "react": "^15.5.4",
    "react-dom": "^15.5.4"
  }
}

Un peu de prog côté serveur

package.json

Path Param

const { createServer } = require('http');
const { parse } = require('url');
const next = require('next');
const pathMatch = require('path-match');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const route = pathMatch();

const match = route('/rennesjs/:param');

app.prepare().then(() => {
  createServer((req, res) => {
    const { pathname, query } = parse(req.url, true);
    const params = match(pathname);

    if (params === false) {
      handle(req, res);
      return;
    }

    app.render(req, res, '/sub/path', Object.assign(params, query));
  }).listen(3000, err => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

server.js

Path Param

export default ({ url: { query: { id, param } } }) => (
  <div>
    <h1>Mon identifiant est : {id} et mon chemin est : {param}</h1>
  </div>
);

/pages/sub/path.js

Links

import Link from 'next/link';

export default ({ url: { query: { id, param } } }) => (
  <div>
    <h1>Mon identifiant est : {id} et mon chemin est : {param}</h1>

    <Link href="/rennesjs">
      <a title="Retour à Rennes Js">
        plip
      </a>
    </Link>
  </div>
);

/pages/sub/path.js

Prefetch


    <Link prefetch href="/rennesjs">
      <a title="Retour à Rennes Js">
        plip
      </a>
    </Link>
 
import Router from 'next/router'


...

{
    Router.prefetch('/rennesjs')
}

Layouts

import Link from 'next/link';
import Head from 'next/head';

export default ({ children, title = 'This is the default title' }) => (
  <div>
    <Head>
      <title>{title}</title>
      <meta charSet="utf-8" />
      <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    </Head>
    <header>
      <nav>
        <Link href="/"><a>Home</a></Link> |
        <Link href="/rennesjs"><a>Rennes JS</a></Link> |
      </nav>
    </header>

    {children}

    <footer>
      Je suis en bas mais je suis bien
    </footer>
  </div>
);

/components/layout.js

Layouts

import Link from 'next/link';
import Layout from '../../components/layout';

export default ({ url: { query: { id, param } } }) => (
  <Layout>
    <h1>Mon identifiant est : {id} et mon chemin est : {param}</h1>

    <Link href="/rennesjs">
      <a title="Retour à Rennes Js">
        plip
      </a>
    </Link>
  </Layout>
);

/pages/sub/path.js

Data

import Link from 'next/link';
import Layout from '../../components/layout';
import axios from 'axios';

const path = ({ url: { query: { id, param } }, avatar }) => (
  <Layout>
    <h1>Mon identifiant est : {id} et mon chemin est : {param}</h1>

    <Link href="/rennesjs">
      <a title="Retour à Rennes Js">
        plip
      </a>
    </Link>
    <br />
    <img src={avatar} width="60px" />
  </Layout>
);

path.getInitialProps = async ({ req }) => {
  const res = await axios.get('https://api.github.com/users/mlecoq/events');
  return { avatar: res.data[0].actor.avatar_url };
};

export default path;

/pages/sub/path.js

Data

/pages/sub/path.js

State (Redux)

~ yarn add redux

~ yarn add react-redux

~ yarn add next-redux-wrapper

State (Redux)

import { createStore } from 'redux';

export const actionTypes = {
  ADD: 'ADD',
  REMOVE: 'REMOVE',
};

const reducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case actionTypes.ADD:
      return { ...state, count: state.count + 1 };
    case actionTypes.REMOVE:
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export const makeStore = initialState => {
  return createStore(reducer, initialState);
};

export function addCount() {
  return { type: actionTypes.ADD };
}

export function removeCount() {
  return { type: actionTypes.REMOVE };
}

Store

State (Redux)

import withRedux from 'next-redux-wrapper';
import Link from 'next/link';
import { bindActionCreators } from 'redux';
import { makeStore, addCount } from '../store/store';

const AddCounter = ({ add, count }) => (
  <div>
    <h1>Compteur: <span>{count}</span></h1>
    <button onClick={add}>Plus !</button>
    <br />
    <Link href="/remove">Moins</Link>
  </div>
);

const mapStateToProps = ({ count }) => ({ count });

const mapDispatchToProps = dispatch => ({
  add: bindActionCreators(addCount, dispatch),
});

export default withRedux(makeStore, mapStateToProps, mapDispatchToProps)(
  AddCounter,
);

pages/add.js

State (Redux)

import withRedux from 'next-redux-wrapper';
import Link from 'next/link';
import { bindActionCreators } from 'redux';
import { makeStore, addCount } from '../store/store';

const AddCounter = ({ add, count }) => (
  <div>
    <h1>Compteur: <span>{count}</span></h1>
    <button onClick={add}>Plus !</button>
    <br />
    <Link href="/remove">Moins</Link>
  </div>
);

AddCounter.getInitialProps = ({ store }) => {
  store.dispatch(addCount());
};

const mapStateToProps = ({ count }) => ({ count });

const mapDispatchToProps = dispatch => ({
  add: bindActionCreators(addCount, dispatch),
});

export default withRedux(makeStore, mapStateToProps, mapDispatchToProps)(
  AddCounter,
);

pages/add.js

Et si je préfère Vue

Deploiement avec now

Site Statique

Next 3.0

  • Dynamic Import & Export
  • Export en site statique
import dynamic from 'next/dynamic'

const DynamicComponent1 = dynamic(import('../components/hello1'))
const DynamicComponent2 = dynamic(import('../components/hello2'))

export default () => (
  <div>
    <Header />
    <DynamicComponent />
    <p>HOME PAGE is here!</p>
  </div>
)

Autres projets oss de Zeit

Micro Service

Hyper

Pkg

pkg . --targets node6-macos-x64

Arunoda Susiripala, de MeteorJS à Next.js

By Mickael Lecoq

Arunoda Susiripala, de MeteorJS à Next.js

  • 2,480