Nicessr - ES6-based simple framework for server-side-rendering
Simple server-side-rendering framework for static HTML generation. It lets you render markup using JSX syntax using props provided from server.
- Create project:
npm init -y
- Install nicessr:
npm install nicessr
- Create
src/pages
directory - Create
index.js
inside, add some code from examples below - Run
npx nicessr start
- Open browser in
http://localhost:9000
start
: Start development server with auto reloadingbuild
: Build production bundleserve
: Serve production bundle (note: You should runbuild
command beforeserve
)export
: Export static HTML and other assets (note: If you use app context, there will not bereq
andres
props)
You should create pages under src/pages
directory. Page file should look like this:
function HomePage() {
return <h1>Welcome to my home page!</h1>;
}
export default HomePage;
Pages should always have default export: function returning html. You can also leverage server-side rendering by creating getInitialProps
exported function. It could be async, example:
export async function getInitialProps() {
return {
number: await new Promise((resolve) => setTimeout(() => resolve(4), 1000)),
};
}
function HomePage({ number }) {
return <p>My favourite number is: ${number}</p>;
}
export default HomePage;
You can also attach functional props to html. There is onMount
hook which will be called after DOM rendering and attaching event listeners. The only argument passed is element that has onMount
hook:
function Page() {
return (
<p
onMount={(node) => {
console.log(node);
}}
>
Hi
</p>
);
}
export default Page;
When page is loaded, <p>
element will be printed in console.
Also you can attach event listeners to components. Event names are as like the names in addEventListener
function.
function Page() {
return <button click={() => console.log("I'm clicked!")}>Click me</button>;
}
export default Page;
When you click this button, I'm clicked!
will be printed to console.
You can call useRef
hook provided by nicessr
package. Pass value returned by it to component, and its value will be set with rendered DOM node. Example:
import { useRef } from 'nicessr';
function Home() {
let counter = 0;
const textRef = useRef();
return (
<>
<p ref={textRef}>{counter}</p>
<button click={() => (textRef.current.innerText = ++counter)}>
Click me!
</button>
</>
);
}
export default Home;
In example below, textRef.current
value was set to <p>
element during initial render. You can also use ref callback:
<p ref={node => { /* Do something */ }}>
You can add <head>
tag to your page. Example:
function HomePage() {
return (
<>
<head>
<title>My home page</title>
</head>
<p>This is home page!</p>
</>
);
}
export default HomePage;
You can include css in your components! Example:
import { useRef } from 'nicessr';
const textStyles = css`
color: red;
font-size: 14px;
background-color: white;
`;
function Home() {
let counter = 0;
const textRef = useRef();
return (
<>
<p class={textStyles} ref={textRef}>
{counter}
</p>
<button click={() => (textRef.current.innerText = ++counter)}>
Click me!
</button>
</>
);
}
export default Home;
There is useForm
hook for convenient collecting form values. It accepts one argument, callback
, which will be called with form values object after successful validation. Example:
import { useForm } from 'nicessr';
function FormPage() {
const [formRef, handleSubmit] = useForm((values) => {
console.log(values); // Possible output: {"name": "123", "link": "https://example.com"}
});
// Pass formRef and handleSubmit functions to <form> tag
<form ref={formRef} submit={handleSubmit}>
<div>
<label>Name</label>
<div>
<input name="name" type="text" required />
</div>
</div>
<div>
<label>Link</label>
<div>
<input name="link" type="url" required />
</div>
</div>
<div>
<input type="submit" value="Submit" />
</div>
</form>;
}
export default FormPage;
You can use css tagged template literal for styles. This will be rendered only on server side, and be included in <head>
tag of page. Classname will be as short as possible. Also you can pass array to class
prop of component:
const flexSpaceBetween = css`
display: flex;
justify-content: space-between;
`;
const redText = css`
color: red;
`;
function Home() {
return (
<div class={[flexSpaceBetween, redText]}>
<p>1</p>
<p>2</p>
</div>
);
}
export default Home;
You can import CSS files using import
statements. They will be rendered as <link>
tags in the head of page. Example (you should install Bootstrap first: npm i bootstrap
):
import 'bootstrap/dist/css/bootstrap.min.css';
function Page() {
return (
<div class="alert alert-primary" role="alert">
A simple primary alert—check it out!
</div>
);
}
export default Page;
You can use dangerouslySetInnerHTML
prop of manual setting HTML for node. Note: node with dangerouslySetInnerHTML
must not have any children.
function Text() {
return <p dangerouslySetInnerHTML="Hello!" />;
}
export default Text;
Value returned from getInitialProps
function will be passed as first argument to page component.
There is appContext
instance passed to getInitialProps
function. It contains req
, res
props:
express Request
and Response
objects. You can also extend it by creating src/pages/_app.js
file:
src/pages/_app.js
:
import { MongoClient } from 'mongodb';
// Dispose is called when _app.js is updated, use it for cleanup.
// For example: close connection to database, clearTimeouts, etc.
export function dispose(ctx) {
ctx.client.close();
}
// Default export is called only on start and when _app.js is updated.
// It should return object that extends app context.
export default async function init() {
const client = await MongoClient.connect('mongodb://localhost:27017');
return { client, db: client.db('my-app') };
}
src/pages/index.jsx
:
// There are { client, db, req, res } props passed to getInitialProps function.
// { client, db } are created by init function in _app.js,
// { req, res } are express request and response objects
export async function getInitialProps({ db }) {
return { items: await db.collection('items').find().toArray() };
}
function Home({ items }) {
return <p>{JSON.stringify(items)}</p>;
}
export default Home;
You can define server-side functions for your page. They will be passed as functions
prop to your page component. Example:
// Note that this function will not be available on client side as like getInitialProps.
export function serverSideFunctions() {
// Return object with all functions. They accept application context extended with request and response objects as argument. Return value is sent back to the client.
return {
add: async ({
req: {
body: { a, b },
},
}) => ({ number: a + b }),
};
}
function Home({ functions }) {
return (
<button
click={async () => {
console.log(await functions.add({ a: 2, b: 3 }));
}}
>
Click me!
</button>
);
}
export default Home;
There are extra babel plugins ran on builds (check src/compiler/babel/
):
strip-dev-code
: Removesif (process.env.NODE_ENV === 'development')
statements on production bundle (both SSR and client bundles)
Check CONTRIBUTING.md
Webpack is used internally for build (check out src/compiler/index.ts
). Pages are built on-demand (only when are requested, look at src/compiler/bundler/entrypoints.ts
), called on server-side using dynamic require
(check out src/ssr/index.ts
).
-
Init function for app context is called in
_app.js
-
Page component is being imported on server
-
getInitialProps
andserverSideFunctions
are called on server -
default
export of page module is being called on server -
Fibers are rendered to string and final markup is being sent to the client
-
Hydration is being performed on
default
export of page component -
Ref
s are being attached to corresponding DOM nodes -
Event listeners are being attached to DOM nodes
-
onMount
hooks are being called from queue