GioJSdocs
On this page

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

FileHow you use itWhat GioJS does
Any .css, importedimport './globals.css'Bundles it into the importing route's stylesheet and links it in <head>
*.module.cssimport 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, error or not-found file, 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 and Cache-Control: public, max-age=31536000, immutable. Production output is minified unless [css] minify = false.
  • @import is bundled, a bare package name included (@import "modern-normalize";), and files referenced with url() 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.css has local class names, ids and @keyframes. Its default export maps each local name to the generated one: .title in card.module.css becomes card_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; and composes: 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 .css file under app/ except *.module.css is transformed with Lightning CSS when the server starts and served from memory at its path inside app/: app/globals.css at /globals.css, app/_styles/print.css at /_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-revalidate and a strong ETag: browsers revalidate on each use and get a 304 while the file is unchanged.
  • On cached pages that link no imported stylesheet, app/globals.css is 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 = false turns 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.css files are never served by path (a request for one is a 404): 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 export and gio build standalone ship 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.

Version history

VersionChanges
v0.1.0-beta.8CSS 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.1Introduced: app/ stylesheets transformed with Lightning CSS at startup and served from memory, with critical CSS extraction.