@travelshift/web v1.0.0
Monorepo TS Next.js application
Setting up the local dev environment
- run
yarn --pure-lockfile
. Also run it on the parentjs
folder - create .env file in this folder
- run
yarn dev
Try http://localhost:3000/book-holiday-trips/6-day-self-drive-tour-circle-of-iceland or some other known URL to confirm that everything is up and running.
Tests
We are using react-testing library as our testing framework. Tests are usually placed next to the tsx files with the .test.tsx
extension.
yarn test
will run your unit tests and npx jest --watchAll
will watch for changes and re-run any test file that is modified.
We are not aiming for a 100% coverage but please do your best.
Deployment
Every branch is deployed automatically at creation, and redeployed when it's updated, to [branch-name]-web.branch.dev.traveldev.org
and it will be live for 48hs. If you need it again after that time frame you can redeploy it from the CI.
Every merged branch will be deployed to production. There's no need to squash commits or any other cleaning up to get a PR merged, but please check that your merged branches were deleted.
Styles
For responsiveness we start from the mobile styles and override them with media queries to fit other resolutions.
On the React components we use emotion.js for CSS-in-JS which provides us with mixin-like structures, variables and so on.
The styles are kept in check by stylelint rules. This work almost seamlessly for the CSS-in-JS code.
Check CSS-in-JS notes & workarounds for more information.
Translations
We use ICU expression through i18next-icu and next-i18next libraries.
There's a <Trans/>
component that should be used for i18n.
import { Trans } from "i18n";
const Content = () => {
return (
<Trans>Some i18n enabled string</Trans>
)
};
Also we have an useTranslation
hooks for translating strings which is useful whenever the translated text can't be expressed as the content of a component (i.e. translating an attribute or sending a translation as a parameter)
import { useTranslation } from "i18n";
const FooterContainer = ({ theme }: Props) => {
const { t } = useTranslation("footer");
return (
<Footer
socialMediaSectionText={t("front_footer_social_media")}
/>
);
};
Regarding the actual translation of strings we are not usually re-using those from the legacy pages but creating new ones.
Routing
Base routes are defined on src/shared/routes.ts
Data management
We are not using any state manager, just contexts and hooks.
To query GraphQL we have 1 query per page that's passed to the getInitialProps method
import tourQuery from "components/features/Tour/queries/TourQuery.graphql";
(TourPage as any).getInitialProps = () => ({
namespacesRequired: ["common", "footer", "header"],
query: tourQuery,
});
Queries are cached automatically by the server and on the client. There's a base Component to add apollo functionality to the other ones
import App from "next/app";
import withApolloClient from "lib/withApolloClient.jsx";
class MyApp extends App<Props> {
// ...
}
export default withApolloClient(MyApp);
Optimizations
- Lazy hydration (check the example)
- Waypoints
- Lazy components
More references
Creating .env file with the following keys
CLIENT_API_URI=guidetoiceland.is
JAEGER_COLLECTOR_ENDPOINT=http://jaeger-collector.dev.traveldev.org/api/traces
JAEGER_SERVICE_NAME=<your-name>
SENTRY_DSN=<SENTRY_DSN_VALUE>
JAEGER_SERVICE_NAME will be useful for marking your logs with the jaeger service.
CSS-in-JS notes & workarounds
stylelint gets a bit confused when interpolating variables so the following syntax is a preferred way to sum up rules:
styled.div([
hideScrollbar,
css`
height: calc(100% - 80px);
overflow-y: scroll;
overscroll-behavior: contain;
`,
]);
Instead of directly interpolating hideScrollbar inside the css
string template.
Lazy Hydration example
const TourContent = ({
name,
ribbon,
valuePropositions,
images,
reviewTotalScore,
reviewTotalCount,
tourSections,
}: Props) => (
<>
<LazyHydrate whenIdle>
<SectionNavigationBar sections={tourSections} />
</LazyHydrate>
</>
);
External links
5 years ago