Skip to content
This repository has been archived by the owner on Oct 11, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1394 from withspectrum/sweet-sweet-seo
Browse files Browse the repository at this point in the history
Server-side rendering
  • Loading branch information
brianlovin authored Sep 7, 2017
2 parents c8758a9 + 6428c42 commit 60ae8c0
Show file tree
Hide file tree
Showing 77 changed files with 2,225 additions and 1,848 deletions.
2 changes: 2 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
]
],
"plugins": [
"babel-plugin-transform-class-properties",
["styled-components", { "ssr": true }],
"transform-flow-strip-types",
"transform-object-rest-spread",
"babel-plugin-transform-react-jsx",
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ build-hermes
package-lock.json
.vscode
dump.rdb
*.swp
59 changes: 28 additions & 31 deletions build-chronos/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion config-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const rewireStyledComponents = require('react-app-rewire-styled-components');
const swPrecachePlugin = require('sw-precache-webpack-plugin');
const fs = require('fs');
const match = require('micromatch');
const WriteFilePlugin = require('write-file-webpack-plugin');

const isServiceWorkerPlugin = plugin => plugin instanceof swPrecachePlugin;
const whitelist = path => new RegExp(`^(?!\/${path}).*`);
Expand All @@ -29,5 +30,6 @@ const setCustomSwPrecacheOptions = config => {

module.exports = function override(config, env) {
setCustomSwPrecacheOptions(config);
return rewireStyledComponents(config, env);
config.plugins.push(WriteFilePlugin());
return rewireStyledComponents(config, env, { ssr: true });
};
23 changes: 23 additions & 0 deletions docs/backend/iris/server-side-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Server-side rendering

In production we server our React-based frontend (`src/`) server-side rendered, meaning we do an initial render on the server and send down static HTML, then rehydrate with the JS bundle.

## Developing SSR

When you develop Spectrum you're usually running two processes, `yarn run dev:client` for the frontend and `yarn run dev:server` for Iris, the GraphQL API. This means in your browser you access `localhost:3000`, which is a fully client-side React app, and that then fetches data from `localhost:3001/api`.


## When developing SSR features directly:
To test server-side rendering locally just load Spectrum from `localhost:3001` (instead of `:3000`), which will request the HTML from Iris rather than `webpack-dev-server`.

The upside of this setup is that we get the best development experience locally with hot module reloading etc, and in production we only have one SSR process.

The only downside is that when you're testing SSR and changing the frontend those changes won't be reflected. To get changes from the frontend reflected when you're requesting `localhost:3001` you have to:

1. Stop the `yarn run dev:client` process
2. Run `yarn run dev:client`
3. Wait for the first compilation to complete
4. Restart the `yarn run dev:server` process by stopping and then starting it again

## When doing all other client and Iris development:
Just stick to the normal workflow of `yarn run dev:client` and enjoy the hot reloading at `localhost:3000`!
4 changes: 1 addition & 3 deletions iris/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
const env = require('node-env-file');
const IS_PROD = process.env.NODE_ENV === 'production';
const path = require('path');
if (!IS_PROD) {
env(path.resolve(__dirname, './.env'), { raise: false });
}
env(path.resolve(__dirname, './.env'), { raise: false });
// $FlowFixMe
const passport = require('passport');
// $FlowFixMe
Expand Down
44 changes: 11 additions & 33 deletions iris/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import fs from 'fs';
import { createServer } from 'http';
//$FlowFixMe
import express from 'express';
import * as graphql from 'graphql';

import schema from './schema';
import { init as initPassport } from './authentication.js';
Expand Down Expand Up @@ -38,38 +39,12 @@ app.use('/api', apiRoutes);
import stripeRoutes from './routes/stripe';
app.use('/stripe', stripeRoutes);

// In production use express to serve the React app
// In development this is done by react-scripts, which starts its own server
if (IS_PROD) {
const { graphql } = require('graphql');
// Load index.html into memory
var index = fs
.readFileSync(path.resolve(__dirname, '..', 'build', 'index.html'))
.toString();
app.use(
express.static(path.resolve(__dirname, '..', 'build'), { index: false })
);
app.get('*', function(req, res) {
getMeta(req.url, (query: string): Promise =>
graphql(schema, query, undefined, {
loaders: createLoaders(),
user: req.user,
})
).then(({ title, description, extra }) => {
// In production inject the meta title and description
res.send(
index
// Replace "Spectrum" with proper title, but make sure to not replace the twitter site:name
// (which is set to Spectrum.chat)
.replace(/Spectrum(?!\.chat)/g, title)
// Replace "Where communities live." with proper description for page
.replace(/Where communities live\./g, description)
// Add any extra meta tags at the end
.replace(/<meta name="%OG_EXTRA%">/g, extra || '')
);
});
});
}
// Use express to server-side render the React app
const renderer = require('./renderer').default;
app.use(
express.static(path.resolve(__dirname, '..', 'build'), { index: false })
);
app.get('*', renderer);

import type { Loader } from './loaders/types';
export type GraphQLContext = {
Expand All @@ -90,4 +65,7 @@ server.listen(PORT);

// Start database listeners
listeners.start();
console.log(`GraphQL server running at port ${PORT}!`);
console.log(`GraphQL server running at http://localhost:${PORT}/api`);
console.log(
`Web server running at http://localhost:${PORT}, server-side rendering enabled`
);
10 changes: 8 additions & 2 deletions iris/migrations/20170825220615-clean-recurring-payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ exports.up = function(r, conn) {
return Promise.all([
cleanSubscriptions,
// delete all the old records in recurringPayments table
r.table('recurringPayments').delete().run(conn),
r
.table('recurringPayments')
.delete()
.run(conn),
// also create a new index against communityId for faster isPro lookups on communities
r.table('recurringPayments').indexCreate('communityId').run(conn),
r
.table('recurringPayments')
.indexCreate('communityId')
.run(conn),
]);
})
.then(([cleanSubscriptions]) => {
Expand Down
5 changes: 4 additions & 1 deletion iris/migrations/20170829233734-userid-index-on-invoices.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

exports.up = function(r, conn) {
return Promise.all([
r.table('invoices').indexCreate('userId').run(conn),
r
.table('invoices')
.indexCreate('userId')
.run(conn),
]).catch(err => {
console.log(err);
throw err;
Expand Down
10 changes: 8 additions & 2 deletions iris/migrations/20170831163211-invoice-data-model-update.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ exports.up = function(r, conn) {
return Promise.all([
cleanInvoices,
// delete all the old records in recurringPayments table
r.table('invoices').delete().run(conn),
r
.table('invoices')
.delete()
.run(conn),
]);
})
.then(([cleanInvoices]) => {
// insert each new clean record into the table
return cleanInvoices.map(invoice => {
return r.table('invoices').insert(invoice).run(conn);
return r
.table('invoices')
.insert(invoice)
.run(conn);
});
});
};
Expand Down
4 changes: 3 additions & 1 deletion iris/models/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,9 @@ const deleteChannel = (channelId: string): Promise<Boolean> => {
};

const getChannelMemberCount = (channelId: string): number => {
return db.table('channels').get(channelId)('members').count().run();
return db.table('channels').get(channelId)('members')
.count()
.run();
};

module.exports = {
Expand Down
16 changes: 12 additions & 4 deletions iris/models/community.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ const getCommunitiesByUser = (userId: string): Promise<Array<Object>> => {
.zip()
// ensure we don't return any deleted communities
.filter(community => db.not(community.hasFields('deletedAt')))
.filter(row => row('isMember').eq(true).or(row('isOwner').eq(true)))
.filter(row =>
row('isMember')
.eq(true)
.or(row('isOwner').eq(true))
)
.run()
);
};
Expand Down Expand Up @@ -471,9 +475,13 @@ const userIsMemberOfCommunity = (
communityId: string,
userId: string
): Promise<Boolean> => {
return db.table('communities').get(communityId).run().then(community => {
return community.members.indexOf(userId) > -1;
});
return db
.table('communities')
.get(communityId)
.run()
.then(community => {
return community.members.indexOf(userId) > -1;
});
};

const userIsMemberOfAnyChannelInCommunity = (
Expand Down
10 changes: 8 additions & 2 deletions iris/models/invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ import { db } from './db';
import { addQueue } from '../utils/workerQueue';

export const getInvoice = (id: string): Promise<Array<Object>> => {
return db.table('invoices').get(id).run();
return db
.table('invoices')
.get(id)
.run();
};

export const getInvoicesByCommunity = (id: string): Promise<Array<Object>> => {
return db.table('invoices').getAll(id, { index: 'communityId' }).run();
return db
.table('invoices')
.getAll(id, { index: 'communityId' })
.run();
};

export const getInvoicesByUser = (id: string): Promise<Array<Object>> => {
Expand Down
5 changes: 4 additions & 1 deletion iris/models/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import type { PaginationOptions } from '../utils/paginate-arrays';
export type MessageTypes = 'text' | 'media';

const getMessage = (messageId: string): Promise<Object> => {
return db.table('messages').get(messageId).run();
return db
.table('messages')
.get(messageId)
.run();
};

const getMessages = (threadId: String): Promise<Array<Object>> => {
Expand Down
Loading

0 comments on commit 60ae8c0

Please sign in to comment.