Dartvel, home
Docs
Features
Studio
Cloud
Compared

Search the site

GitHub
pub.dev

GETTING STARTED

Getting started
Existing Flutter apps
Existing Native apps
Run on your phone

APP

UI and styling
Routing
State
Accessibility
Keyboard shortcuts
Localization
Devices and desktop
Native device access
Media, 3D and XR

DATA

Data models
Forms
Search
Sync and offline
Import and export
Change capture
Database
Cache
File storage
Images
Privacy and erasure

BACKEND

Backend functions
Auth and sessions
Authorization
Queues and jobs
Workers and memory
Notifications and mail
Outbound HTTP
AI
Webhooks
GraphQL and OpenAPI
API keys and OAuth
Multi-tenancy
Billing and commerce
Modules

OPERATIONS

Edge security
Secrets and environments
Monitoring
Releases

SHIPPING

Build targets
Telegram Mini Apps
Static web hosting
Servers and deploying

REFERENCE

Testing
CLI reference
Coding agents

SHIPPING

Static web hosting

dartvel build web writes static files you can upload to any shared host.

On Apache or LiteSpeed, the .htaccess it writes makes deep links and caching work.

ON THIS PAGE

Build the site

What the build writes

The .htaccess for Apache and LiteSpeed

Install it as an app, and use it offline

Other static hosts

Find text with Ctrl+F

Link to a heading

Build the site

dartvel build web

Copy code to clipboard

Upload the contents of build/web to your web root.

What the build writes

index.html

For: The app shell, with the title, description, Open Graph tags and splash

<route>/index.html

For: One page per route, so a deep link and a crawler get real HTML

manifest.json, icons/

For: The installable PWA

flutter_service_worker.js

For: Offline caching, replaced by Dartvel's worker

offline/index.html, 404/index.html

For: The not-found and offline pages, which are routes of your app

404.html

For: The not-found page again, for the hosts that look for that name

sitemap.xml, robots.txt

For: Search engines, when seo.siteUrl is set

.htaccess

For: Apache and LiteSpeed rules, when seo.siteUrl is set

# pubspec.yaml
dartvel:
  seo:
    siteUrl: https://example.com

Copy code to clipboard

Routes with parameters get pages for the paths your models list.

Set pwa.enabled: false to leave out the manifest and worker.

The .htaccess for Apache and LiteSpeed

This is the file the build writes. LiteSpeed reads the same rules.

# Written by dartvel build web. Rewritten on every build while this line is
# here. To keep your own edits, delete this line: the build then leaves the
# file alone. Or put your own in the project's web/.htaccess, which the build
# copies in place of this one.

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteBase /

  # Served as themselves. Without these the fallback below swallows every
  # asset and the page loads index.html as its own JavaScript.
  RewriteCond %{REQUEST_FILENAME} -f [OR]
  RewriteCond %{REQUEST_FILENAME} -d
  RewriteRule ^ - [L]

  # Everything else is a route. The application reads the URL and renders it.
  RewriteRule . /index.html [L]
</IfModule>

# Where mod_rewrite is off. The rewrite above sends unknown paths to the
# application shell, which is right for a route the router knows and wrong for
# one nothing does -- and it does not run at all on a host without
# mod_rewrite, where this is what a visitor sees instead of the server's own
# branding.
ErrorDocument 404 /404/index.html

# The Universal Links document has no extension for AddType to match, and iOS
# refuses it as anything but JSON.
<Files "apple-app-site-association">
  ForceType application/json
</Files>

<IfModule mod_mime.c>
  # instantiateStreaming refuses a wasm module served as anything else.
  AddType application/wasm .wasm
  AddType application/javascript .js
  AddType application/json .json
</IfModule>

<IfModule mod_headers.c>
  # Not cached, because Flutter does not content-hash any of these: a build
  # from today and a build from last year both produce a file called
  # main.dart.js. A blanket immutable rule over *.js froze the site for every
  # returning visitor -- no error, no way for them to know, and no way out
  # from their side once the service worker was cached too.
  <FilesMatch "^(index\.html|main\.dart\.js|flutter_bootstrap\.js|flutter_service_worker\.js|version\.json|manifest\.json)$">
    Header set Cache-Control "no-cache, must-revalidate"
  </FilesMatch>

  # These do carry a version in their path, and they are the large ones.
  <FilesMatch "^(canvaskit/|assets/).*\.(js|wasm|woff2|otf|ttf|png|jpg|svg|json)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
</IfModule>

Copy code to clipboard

Existing files are served as they are. Every other path loads the app, which routes it.

index.html, main.dart.js and the service worker are checked with the server on every visit, so a new upload reaches returning visitors.

Put your own web/.htaccess in the project and the build copies yours instead.

The build rewrites its own .htaccess every time, so fixes to the rules reach you. Delete the first line of the copy in build/web and later builds leave your edits alone.

Install it as an app, and use it offline

dartvel build web writes the manifest, icons from web/icon.png, and a service worker. A page the worker cannot reach opens /offline, a page of your app that says where you were.

A change sent while offline is queued in the browser and sent in order when the network is back. CI checks this in Chrome on every push.

The install prompt reports what the visitor chose, and can be offered again after a No.

Built

Spec section: PWA

Other static hosts

Any host works if it serves index.html for paths with no file.

dartvel deploy --target web --provider netlify runs the provider's own CLI. firebase-hosting, vercel and cloudflare work the same way.

Find text with Ctrl+F

Ctrl+F (Cmd+F on a Mac), and Find in page from a phone browser's menu, find text on a Dartvel web page. The page scrolls to the paragraph that matched and highlights it.

Flutter draws the page's words on a canvas, and the browser searches the document. So every page keeps a copy of its text in the document where the browser can search it:

The build writes each page's text into its HTML for crawlers, no-script readers and printers. Each paragraph is marked hidden="until-found", which the browser searches and does not draw.

When a match lands in a paragraph, the browser fires beforematch. Dartvel scrolls the Flutter page to that paragraph, highlights it, and hides the copy again.

After you navigate, or when the page's content changes, the copy is rewritten from what the page has drawn. Find and printing follow the page on screen.

There is nothing to add to a page. @DVPage(findable: false) keeps a page's text out of the document.

A browser match names the paragraph and not the word, so the page scrolls to the paragraph. It works in Chrome, Edge and other Chromium browsers, and in Firefox 139 and later. Safari has not been checked yet. A browser without until-found finds nothing on the page, as before.

Esc closes the find bar from the page as well as from the bar. A page answers Esc only when it has something open to close, such as a dialog, a menu or the text selection menu. Otherwise the key goes to the browser.

A list builds only the rows near the screen, so text in a row that has not been built yet is not found.

Planned

An in-app find bar in the page shell, for native builds and for browsers without until-found. Generated record tables that register the rows they have not built, so a match in a row off screen is found too. Neither is built yet.

Read the find in page proposal

Link to a heading

An address ending in #heading-id opens the page at that heading, on the web and in a native app, as it does on any other web page. Flutter ignores the part after #, so a Dartvel page does this itself.

Every heading gets an id from its words: lower case, punctuation dropped, spaces as hyphens. "Find text with Ctrl+F" is #find-text-with-ctrlf.

A heading that repeats an earlier one is numbered from 2: the second "Setup" is #setup-2.

Headings are the text a page marks with semanticHeading(n). There is nothing to add to a page.

A page that loads its content later still opens at the heading: Dartvel looks for it again as the page draws.

Try it: this button goes to /docs/web-hosting#find-text-with-ctrlf.

Go to "Find text with Ctrl+F"
PREVIOUS Telegram Mini Apps Build for Telegram, use host controls and sign in
NEXT Servers and deploying Run the backend, deploy and provision hosts
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom