start method
Sobe o servidor. Se port for nulo, usa a variável de ambiente PORT
(definida pelo jaspr serve/jaspr build) ou 8080.
Implementation
Future<HttpServer> start({int? port}) async {
final resolvedPort =
port ?? int.tryParse(Platform.environment['PORT'] ?? '') ?? 8080;
final meta = MetaTagsBuilder(seo);
final router = Router();
// Geração estática: reporta TODAS as rotas + SEO + 404 ao proxy do
// jaspr_cli na 1ª requisição (dentro do handler, aguardado antes da
// resposta). Assim a fila enche antes do crawler esvaziá-la — evita a
// corrida que deixava só a raiz gerada.
var reportedAll = false;
Future<void> reportGenerateRoutes() async {
if (reportedAll) return;
reportedAll = true;
try {
for (final m in await registry.resolveAll()) {
await ServerApp.requestRouteGeneration(m.path, priority: m.priority);
}
for (final extra in const [
'/robots.txt',
'/sitemap.xml',
'/llms.txt',
'/llms-full.txt',
'/404.html',
]) {
await ServerApp.requestRouteGeneration(extra);
}
} catch (_) {
// Proxy pode fechar durante o shutdown — ignora.
}
}
// /robots.txt, /sitemap.xml, /llms.txt, /llms-full.txt automáticos.
SeoEndpoints(config: seo, registry: registry).mountOn(router);
// APIs declarativas (envelope/validação/paginação/auth padrão).
if (apis.isNotEmpty && db != null) {
FlenxApi(
apis,
db!,
onEmail: onEmail,
tokenVerifier: tokenVerifier,
).mountOn(router);
}
router.mount(
'/',
serveApp((request, render) async {
if (kGenerateMode) await reportGenerateRoutes();
final path = request.requestedUri.path.isEmpty
? '/'
: request.requestedUri.path;
// `/404.html`: serve a página notFound com status 200 (necessário para o
// build estático gravar 404.html — o crawler ignora respostas != 200).
final is404Page = path == '/404.html';
final result = is404Page
? null
: await resolvePage(path, request.requestedUri.queryParameters);
final known = result != null;
final routeMeta =
await registry.find(path) ??
(known
? RouteMeta(
path: path,
title: seo.siteName,
description: seo.description,
)
: const RouteMeta(
path: '/404',
title: '404 — Página não encontrada',
description: 'A página que você procura não existe.',
noindex: true,
));
final document = Document(
title: routeMeta.title,
lang: lang,
styles: [..._resetStyles, ...styleRules],
head: [
...meta.build(routeMeta),
for (final raw in rawStyles)
Component.element(tag: 'style', children: [RawText(raw)]),
for (final img in preloadImages)
link(
rel: 'preload',
href: img,
attributes: const {'as': 'image', 'fetchpriority': 'high'},
),
...headExtra,
if (result?.island ?? false)
script(src: flutterBootstrap, defer: true),
],
body: floatingButtons.isEmpty
? (result?.body ?? notFound)
: Component.fragment([
result?.body ?? notFound,
...floatingButtons,
]),
);
final response = await render(document);
if (known || is404Page) return response;
final html = await response.readAsString();
return Response.notFound(
html,
headers: {'content-type': 'text/html; charset=utf-8'},
);
}),
);
final handler = const Pipeline()
.addMiddleware(logRequests())
.addHandler(router.call);
// Controle de hot-reload do jaspr: fecha o servidor anterior ao recarregar.
final reloadLock = _activeReloadLock = Object();
final server = await serve(
handler,
InternetAddress.anyIPv4,
resolvedPort,
shared: true,
);
if (reloadLock != _activeReloadLock) {
await server.close();
return server;
}
await _activeServer?.close();
_activeServer = server;
// Geração estática (`jaspr build` em mode: static): destrava o crawler
// reportando a raiz (as demais rotas são reportadas na 1ª requisição, no
// handler) e escreve o .htaccess na pasta de saída.
if (kGenerateMode) {
await ServerApp.requestRouteGeneration('/');
if (emitHtaccess) {
const outDir = String.fromEnvironment(
'jaspr.dev.web',
defaultValue: 'build/jaspr',
);
try {
File('$outDir/.htaccess').writeAsStringSync(_htaccess);
} catch (_) {
// Ambiente sem acesso de escrita — ignora silenciosamente.
}
}
}
return server;
}