CSS files
How GioJS treats .css files: global stylesheets you import, *.module.css files with local class names, and app/*.css files served by their path.
app/layout.tsx
import React from 'react';
import type { LayoutProps } from '@gio.js/core';
import './globals.css';
export default function RootLayout({ children }: LayoutProps) {
return (
<html lang="en">
<head />
<body>{children}</body>
</html>
);
}Reference
| File | How you use it | What GioJS does |
|---|---|---|
Any .css, imported | import './globals.css' | Bundles it into the importing route's stylesheet and links it in <head> |
*.module.css | import styles from './card.module.css' | Makes its class names local and returns the map; bundled like the above |
A non-module .css under app/ | <link href="/globals.css"> | Also serves it at its path from memory (legacy) |
Global CSS imports
- A side-effect import works in any page, layout,
loading,errorornot-foundfile, and in the components they use. Import site-wide styles from the root layout. - When the worker starts, GioJS follows each route's imports and bundles the CSS it reaches with esbuild. Every page links up to two stylesheets: one shared by all pages with the root layout's CSS, and one for the route without what the shared one already holds (left out when there is nothing left). The cascade follows the tree: root layout, then layouts outer to inner, then the page, each file in import order.
- The files go to
.gio/build/static/css/and are served from/_next/static/css/with content-hashed names andCache-Control: public, max-age=31536000, immutable. Production output is minified unless[css] minify = false. @importis bundled, a bare package name included (@import "modern-normalize";), and files referenced withurl()next to the CSS are copied with hashed names.- The links are React stylesheet resources: they land in
<head>, and on client navigation the next route's stylesheets load before it is shown. Never add a<link>for an imported file yourself.
CSS Modules
- A file named
*.module.csshas local class names, ids and@keyframes. Its default export maps each local name to the generated one:.titleincard.module.cssbecomescard_3fa9c1_title. - The names are the same on the server and in the browser, and stable across builds and machines (the hash covers the owning package's name and the file's path in it), so pages hydrate cleanly and stylesheet URLs stay cacheable.
:global(...),composes: a b;andcomposes: a from './other.module.css';follow esbuild's rules. Use the default import; names that are not identifiers are read with brackets (styles['nav-link']).- TypeScript types come from
.gio/css-modules.d.ts(see .gio/). CSS Modules need an ES module project ("type": "module", which every new project has).
Stylesheets served by path
- Every
.cssfile underapp/except*.module.cssis transformed with Lightning CSS when the server starts and served from memory at its path insideapp/:app/globals.cssat/globals.css,app/_styles/print.cssat/_styles/print.css. Private folders and route groups get no special treatment here. - The URLs have no content hash, so they are sent with
Cache-Control: public, max-age=0, must-revalidateand a strongETag: browsers revalidate on each use and get a304while the file is unchanged. - On cached pages that link no imported stylesheet,
app/globals.cssis also the source for critical CSS: the rules the page uses are inlined and the file loads without blocking render ([css] critical_extraction). [css] enabled = falseturns path serving off. It does not affect imported CSS, which is part of the module graph and always bundled.
Examples
A CSS Module in a component
components/card.module.css
.card {
padding: 1rem;
border-radius: 8px;
}
.title {
composes: heading from './typography.module.css';
color: teal;
}components/Card.tsx
import React from 'react';
import styles from './card.module.css';
export function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<article className={styles.card}>
<h2 className={styles.title}>{title}</h2>
{children}
</article>
);
}CSS from an npm package
app/layout.tsx
import React from 'react';
import type { LayoutProps } from '@gio.js/core';
import 'modern-normalize/modern-normalize.css';
import './globals.css';
export default function RootLayout({ children }: LayoutProps) {
return (
<html lang="en">
<head />
<body>{children}</body>
</html>
);
}GioJS does not look inside npm packages' JavaScript for CSS imports, so a component library's stylesheet is imported explicitly.
Good to know
- Pick one way per file: import it or link it by path. A file used both ways loads twice.
*.module.cssfiles are never served by path (a request for one is a404): their class names exist only in the import pipeline.- Global CSS stays global after navigation: once a route's stylesheet has loaded, it stays in the document. Keep page-specific styles in CSS Modules.
- In development a change to any CSS file rebuilds the stylesheets, restarts the worker and reloads open tabs.
gio exportandgio build standaloneship the same stylesheets and class names. - There is no Sass, Less or PostCSS step. Tailwind runs as its own CLI next to the server; see the Tailwind guide.
- Stylesheets in
public/are plain static files.
Related
- CSS & Styling - the guide.
[css]-enabled,minify,critical_extraction.- layout.tsx, .gio/
Version history
| Version | Changes |
|---|---|
v0.1.0-beta.8 | CSS imports and CSS Modules: per-route, content-hashed, minified stylesheets linked in <head>; .gio/css-modules.d.ts. Path-served stylesheets revalidate with an ETag instead of being cached for a year; CSS Module files are no longer served by path. [css] minify also covers the bundled stylesheets. |
v0.1.0-beta.1 | Introduced: app/ stylesheets transformed with Lightning CSS at startup and served from memory, with critical CSS extraction. |