</br>
⚠️ Direct pushes to
mainare blocked by CI unless the commit message includes[override-main]. Use Pull Requests intomain, or merge fromstaging.
git clone git@github.com:CFsylvester/next.js-tailwind-typescript-TEMPLATE.git [your-repo-name]
cd [your-repo-name]
# Set Node.js version (if using nvm)
nvm use
yarn install
yarn dev
Open http://localhost:3000 with your browser to see the result.
When you first open this project in VS Code, you’ll automatically be prompted to install the recommended extensions in the bottom right corner. You can also install all recommended extensions at once:
Cmd/Ctrl + Shift + PThis project includes a .vscode folder with shared settings for VS Code users. These settings will:
These settings ensure that:
This project uses:
.nvmrc for Node.js version management (currently set to v20)engines in package.json to enforce Node.js (>=20.0.0) and Yarn (>=1.22.0) versionspackageManager field in package.json to specify exact Yarn version (1.22.22)To automatically use the correct Node.js version:
nvm use in the project directoryThe project is configured to use Yarn 1.22.22. When you run yarn install, it will verify that you’re using a compatible version of Yarn. If you have an incompatible version, Yarn will show an error message indicating which version you need to use.
Core styling configuration files:
tailwind.config.js - Configuration including:
src/styles/globals.scss - Global styles including:
- Automatically adds vendor prefixes to CSS rules
- Linting utility for JavaScript and TypeScript
- Code formatter for consistent code style
yarn dev - Run development serveryarn build - Build for productionyarn start - Start production serveryarn lint - Run ESLintyarn lint:fix - Fix ESLint errorsyarn format - Format code with Prettieryarn check-format - Check code formattingThis template includes a built-in GitHub Actions workflow (ci.yml) that runs automatically on all pull requests and pushes to main and staging.
next build)[override-main] in commit message to push directlyAll CI checks must pass before merging into
mainorstaging.
Preview deployments are automatically handled by Vercel for all branches and PRs.
| Branch | Environment | Deployment |
|---|---|---|
main |
Production | ✅ Auto-deploy to prod |
staging |
Staging | ✅ Auto-deploy to preview/staging |
| feature PR | Preview | ✅ Deploy Preview via Vercel |
The grid system uses the following breakpoints (defined in tailwind.config.cjs):
screens: {
xs: '320px', // 4 cols
sm: '480px', // 4 cols
md: '592px', // 6 cols
lg: '784px', // 8 cols
xl: '976px', // 10 cols
'2xl': '1168px', // 12 cols
'3xl': '1360px', // 14 cols
'4xl': '1552px', // 16 cols
}
The grid system relies on dynamic scss variables updated at the above breakpoints to coincide with a responsive layout that is always divisible by multiples of 2. These dynamic, responsive, variables are used in both the .layout and [data-grid-overlay] scss selectors.
:root {
// dynamic variables
--layout-cols: 4;
--layout-padding: theme('spacing.4');
// hardcoded gap
--layout-gap: theme('spacing.4');
// clamp layout col width
--layout-col-width: clamp(
60px,
calc(
(100vw - (2 * var(--layout-padding)) - ((var(--layout-cols) - 1) * var(--layout-gap))) /
var(--layout-cols)
),
124px
);
@screen md {
--layout-cols: 6;
--layout-padding: theme('spacing.6');
}
@screen lg {
--layout-cols: 8;
--layout-padding: theme('spacing.8');
}
@screen xl {
--layout-cols: 10;
--layout-padding: theme('spacing.10');
}
@screen 2xl {
--layout-cols: 12;
--layout-padding: theme('spacing.12');
}
@screen 3xl {
--layout-cols: 14;
--layout-padding: theme('spacing.14');
}
@screen 4xl {
--layout-cols: 16;
--layout-padding: theme('spacing.16');
}
}
With a hardcoded variable of --layout-gap: theme('spacing.4') (16px), the grid remains fully responsive and divisible by multiples of 2.
Responsive Columns & Padding:
320px): --layout-cols: 4; / --layout-padding: theme('spacing.4') (16px)480px): --layout-cols: 4; / --layout-padding: theme('spacing.4') (16px)592px): --layout-cols: 6; / --layout-padding: theme('spacing.6') (24px)784px): --layout-cols: 8; / --layout-padding: theme('spacing.8') (32px)976px): --layout-cols: 10; / --layout-padding: theme('spacing.10') (40px)1168px): --layout-cols: 12; / --layout-padding: theme('spacing.12') (48px)1360px): --layout-cols: 14; / --layout-padding: theme('spacing.14') (56px)1552px): --layout-cols: 16; / --layout-padding: theme('spacing.16') (64px)Clamp Layout Column Width:
--layout-col-width: clamp(
60px,
calc(
(100vw - (2 * var(--layout-padding)) - ((var(--layout-cols) - 1) * var(--layout-gap))) /
var(--layout-cols)
),
124px
);
This expression dynamically adjusts the column width based on the current --layout-padding and --layout-cols values at each breakpoint.
A responsive grid container that adapts to the dynamic variables defined in the layout.scss :root. It is utilized on the main element in the server rendered layout.tsx found in the app directory.
.layout {
@apply grid
h-full
justify-center
gap-x-[var(--layout-gap)]
px-[var(--layout-padding)]
grid-cols-[repeat(var(--layout-cols),var(--layout-col-width))];
> .module {
@apply h-fit z-0;
&:not(:only-child) {
@apply py-module;
}
&:only-child {
@apply my-auto;
}
}
//... &[data-grid-overlay] logic below
}
A purely css driven grid overlay that coincides with the layout class and it’s breakpoints. When the data-overlay-grid is set to active, the grid overlay fades into visibility. It is dependent on being used on the same element as the .layout class. This is utilized on the main element in the server rendered layout.tsx found in the app directory.
.layout {
// ...layout logic above
&[data-grid-overlay] {
@apply mx-auto bg-repeat-x z-10;
width: calc(
var(--layout-cols) * var(--layout-col-width) + (var(--layout-cols) - 1) * var(--layout-gap)
);
background-image: repeating-linear-gradient(
to right,
rgba(255, 0, 0, 0.2) 0,
rgba(255, 0, 0, 0.2) var(--layout-col-width),
transparent var(--layout-col-width),
transparent calc(var(--layout-col-width) + var(--layout-gap))
);
background-size: calc(var(--layout-col-width) + var(--layout-gap)) 100%;
opacity: 0;
visibility: hidden;
transition:
opacity 0.3s ease-in-out,
visibility 0.3s ease-in-out;
&[data-grid-overlay='active'] {
opacity: 1;
visibility: visible;
}
}
}
GridOverlayToggle: A client-side button that:
data-grid-overlay attribute on the <main> element[data-grid-overlay]: A purely CSS-driven overlay that:
--layout-cols, --layout-col-width, --layout-gap, etc.) to match the current layoutdata-grid-overlay="active" is set by the GridOverlayToggleA client side button that triggers the css overlay grid. Updates the main element data-grid-overlay with the active state.
const [active, setActive] = useState(false);
const overlayRef = useRef<HTMLElement | null>(null);
// Grab the element with [data-grid-overlay] once on mount
useEffect(() => {
overlayRef.current = document.querySelector('[data-grid-overlay]');
}, []);
// Update the attribute whenever `active` changes
useEffect(() => {
if (overlayRef.current) {
overlayRef.current.setAttribute('data-grid-overlay', active ? 'active' : '');
}
}, [active]);
Add the grid system to your server render layout found in layout.tsx within the app directory:
// check env vars
const devMode = process.env.NODE_ENV === 'development';
const isGridOverlayOverride = process.env.GRID_OVERLAY_OVERRIDE === 'true';
// show grid overlay if dev mode is true or if the grid overlay override is true
const showGridOverlay = devMode || isGridOverlayOverride;
return (
<html lang="en" className={montserrat.variable}>
<body>
{/* DEV GRID TOGGLE */}
{showGridOverlay && <GridOverlayToggle />}
{/* MAIN CONTENT */}
{/* GRID OVERLAY relies on the layout class */}
<main data-grid-overlay className={'layout'}>
{children}
</main>
</body>
</html>
);