diff --git a/backend/scaffolder/templates/react-ssr-template/cookiecutter.json b/backend/scaffolder/templates/react-ssr-template/cookiecutter.json
new file mode 100644
index 0000000000..3a1b2ae344
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/cookiecutter.json
@@ -0,0 +1,5 @@
+{
+ "component_id": "",
+ "owner": "",
+ "description": "I promise to update this description // {{cookiecutter.owner}}",
+}
diff --git a/backend/scaffolder/templates/react-ssr-template/custom-fields.json b/backend/scaffolder/templates/react-ssr-template/custom-fields.json
new file mode 100644
index 0000000000..1acacc3a25
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/custom-fields.json
@@ -0,0 +1,13 @@
+{
+ "schema": [{
+ "id": "owner",
+ "title": "owner",
+ "type": "text",
+ "description": "The owner name used to identify the owner of this component",
+ "validators": [
+ { "type": "required" },
+ { "type": "string-range", "min": 4, "max": 33 },
+ { "type": "regex", "match": "^[a-z][a-z0-9]+$", "message": "Owner names must consist only of lowercase letters" }
+ ]
+ }]
+}
diff --git a/backend/scaffolder/templates/react-ssr-template/hooks/post_gen_project.sh b/backend/scaffolder/templates/react-ssr-template/hooks/post_gen_project.sh
new file mode 100644
index 0000000000..c6d477d91a
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/hooks/post_gen_project.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# package name is "__component_id__" so that yarn doesn't throw an error
+# about invalid characters when running yarn commands. here we replace it with the actual name
+sed -i -e "s/__component_id__/{{ cookiecutter.component_id }}/g" package.json
+
+# node_modules was moved out of the template folder, during the pre_gen hook,
+# to avoid cookie_cutter from copying all of them. time to move it back
+mv ../../node_modules.tmp ../../\{\{cookiecutter.component_id\}\}/node_modules 2>/dev/null ||:
+
+# move back the build directory that was moved out in the pre_gen hook (if it exists)
+mv ../../build.tmp ../../\{\{cookiecutter.component_id\}\}/build 2>/dev/null ||:
\ No newline at end of file
diff --git a/backend/scaffolder/templates/react-ssr-template/hooks/pre_gen_project.sh b/backend/scaffolder/templates/react-ssr-template/hooks/pre_gen_project.sh
new file mode 100644
index 0000000000..142fda9ad9
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/hooks/pre_gen_project.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# no way to ignore files in cookiecutter, so move node_modules out while building
+# to avoid cookiecutter from copying all of them
+mv ../../\{\{cookiecutter.component_id\}\}/node_modules ../../node_modules.tmp 2>/dev/null ||:
+
+# cookicutter really doesn't like the next.js build directory, so if the app has
+# been built from inside the template folder, that folders needs to be moved out as well
+mv ../../\{\{cookiecutter.component_id\}\}/build ../../build.tmp 2>/dev/null ||:
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.editorconfig b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.editorconfig
new file mode 100644
index 0000000000..23c3268a0f
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.editorconfig
@@ -0,0 +1,13 @@
+# editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+insert_final_newline = false
\ No newline at end of file
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintignore b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintignore
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintignore
@@ -0,0 +1 @@
+
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintrc.js b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintrc.js
new file mode 100644
index 0000000000..8623614899
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@spotify/web-scripts/config/eslintrc.js');
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.gitignore b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.gitignore
new file mode 100644
index 0000000000..4d1e0d9635
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.gitignore
@@ -0,0 +1,18 @@
+# See https://help.github.com/ignore-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+
+# testing
+/coverage
+junit.xml
+
+# build
+/build
+
+# misc
+.DS_Store
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.nvmrc b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.nvmrc
new file mode 100644
index 0000000000..b009dfb9d9
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/.nvmrc
@@ -0,0 +1 @@
+lts/*
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/README.md b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/README.md
new file mode 100644
index 0000000000..415065179b
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/README.md
@@ -0,0 +1,14 @@
+# {{ cookiecutter.component_id }}
+
+## Description
+
+{{ cookiecutter.description }}
+
+## Develop
+
+```bash
+# install dependencies
+$ yarn
+# start development server
+$ yarn dev
+```
\ No newline at end of file
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/babel.config.js b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/babel.config.js
new file mode 100644
index 0000000000..038a71835e
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/babel.config.js
@@ -0,0 +1,9 @@
+module.exports = {
+ plugins: [
+ 'babel-plugin-styled-components'
+ ],
+ presets: [
+ 'next/babel',
+ '@zeit/next-typescript/babel'
+ ]
+};
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/jest.config.js b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/jest.config.js
new file mode 100644
index 0000000000..0dc2a9c0ca
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/jest.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ ...require('@spotify/web-scripts/config/jest.config.js'),
+ testEnvironment: 'jsdom',
+ testPathIgnorePatterns: ['/node_modules/', '/build/'],
+ transform: {
+ '^.+\\.tsx?$': 'babel-jest',
+ },
+};
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/next-env.d.ts b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/next-env.d.ts
new file mode 100644
index 0000000000..fd10310f68
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/next-env.d.ts
@@ -0,0 +1,5 @@
+// read more about this file here ---> https://github.com/zeit/next.js/blob/canary/docs/basic-features/typescript.md
+/* eslint spaced-comment: ["error", "always", { "markers": ["/"] }] */
+
+///
+///
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/next.config.js b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/next.config.js
new file mode 100644
index 0000000000..be8b159307
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/next.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ distDir: 'build',
+};
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/package.json b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/package.json
new file mode 100644
index 0000000000..cf9d469e39
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "__component_id__",
+ "version": "0.0.0",
+ "description": "{{ cookiecutter.description }}",
+ "license": "UNLICENSED",
+ "scripts": {
+ "dev": "next",
+ "build": "next build",
+ "start": "next start",
+ "lint": "web-scripts lint --ignore-path=.gitignore",
+ "test": "web-scripts test --config jest.config.js",
+ "pretest:ci": "yarn lint",
+ "test:ci": "yarn test --ci --coverage --reporters=default --reporters=jest-junit"
+ },
+ "dependencies": {
+ "@zeit/next-typescript": "^1.1.1",
+ "babel-plugin-styled-components": "^1.10.6",
+ "next": "^9.1.1",
+ "react": "^16.8.5",
+ "react-dom": "^16.8.5",
+ "styled-components": "^4.3.2"
+ },
+ "devDependencies": {
+ "@spotify/tsconfig": "^5.0.0",
+ "@spotify/web-scripts": "^5.0.0",
+ "@testing-library/react": "^8.0.1",
+ "@types/node": "^13.1.4",
+ "@types/react": "^16.8.7",
+ "@types/react-dom": "^16.8.2",
+ "@types/styled-components": "^4.1.18",
+ "husky": "^2.7.0",
+ "jest-junit": "^8.0.0",
+ "typescript": "^3.4.5"
+ },
+ "husky": {
+ "hooks": {
+ "pre-commit": "web-scripts precommit"
+ }
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/prettier.config.js b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/prettier.config.js
new file mode 100644
index 0000000000..93df970dd6
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/prettier.config.js
@@ -0,0 +1 @@
+module.exports = require('@spotify/web-scripts/config/prettier.config.js');
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/public/static/fonts.css b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/public/static/fonts.css
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/__tests__/index.test.tsx b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/__tests__/index.test.tsx
new file mode 100644
index 0000000000..8612ead08f
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/__tests__/index.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import { cleanup, render } from '@testing-library/react';
+
+import Index from '../pages/index';
+
+afterEach(cleanup);
+
+describe('Index', () => {
+ it('Says hello', () => {
+ const { queryByText } = render();
+ expect(queryByText('Hello!')).toBeTruthy();
+ });
+});
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/components/Header.tsx b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/components/Header.tsx
new file mode 100644
index 0000000000..eebee43662
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/components/Header.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+export const Header = () => (
+
+ Header
+
+)
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_app.tsx b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_app.tsx
new file mode 100644
index 0000000000..a3853e47c9
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_app.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import App from 'next/app';
+import Head from 'next/head';
+import styled from 'styled-components';
+import { Header } from '../components/Header';
+
+const StyledApp = styled.div`
+ > * {
+ padding-left: 16px;
+ padding-right: 16px;
+ }
+`;
+
+const Main = styled.div`
+ margin: 2em auto;
+ height: 85vh;
+`;
+
+class CustomApp extends App {
+ render() {
+ const { Component, pageProps } = this.props;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default CustomApp;
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_document.tsx b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_document.tsx
new file mode 100644
index 0000000000..210de6412c
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/_document.tsx
@@ -0,0 +1,41 @@
+/**
+ * This file extends the and injects the server side rendered styles into the
+ * By server-side rendering CSS we avoid visual changes in the layout while loading the JS.
+ *
+ * Taken from this example:
+ * https://github.com/zeit/next.js/tree/master/examples/with-styled-components
+ */
+
+import React from 'react';
+import Document, { DocumentContext, DocumentInitialProps } from 'next/document';
+import { ServerStyleSheet } from 'styled-components';
+
+export default class MyDocument extends Document {
+ static async getInitialProps(
+ ctx: DocumentContext,
+ ): Promise {
+ const sheet = new ServerStyleSheet();
+ const originalRenderPage = ctx.renderPage;
+
+ try {
+ ctx.renderPage = () =>
+ originalRenderPage({
+ enhanceApp: (App: any) => props =>
+ sheet.collectStyles(),
+ });
+
+ const initialProps = await Document.getInitialProps(ctx);
+ return {
+ ...initialProps,
+ styles: (
+ <>
+ {initialProps.styles}
+ {sheet.getStyleElement()}
+ >
+ ),
+ };
+ } finally {
+ sheet.seal();
+ }
+ }
+}
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/api/ping.ts b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/api/ping.ts
new file mode 100644
index 0000000000..8bc772c182
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/api/ping.ts
@@ -0,0 +1,5 @@
+import { NextApiRequest, NextApiResponse } from 'next';
+
+export default function handle(_: NextApiRequest, res: NextApiResponse) {
+ res.status(200).send('ok');
+}
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/index.tsx b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/index.tsx
new file mode 100644
index 0000000000..c01d844f9f
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/src/pages/index.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+const Index = () => Hello!
;
+
+export default Index;
diff --git a/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/tsconfig.json b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/tsconfig.json
new file mode 100644
index 0000000000..03508ff043
--- /dev/null
+++ b/backend/scaffolder/templates/react-ssr-template/{{cookiecutter.component_id}}/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "@spotify/tsconfig",
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve"
+ },
+ "exclude": ["node_modules", "output/node_modules"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
+}