run static method
Future<void>
run({
- required ServerOptions options,
- required SeoConfig seo,
- required List<
FlenxRoute> routes, - required Component notFound,
- String? blog,
- Blog? blogInstance,
- List<
BlogSource> blogSources = const [], - bool blogFromDb = false,
- String blogTable = 'blog_posts',
- BlogLayoutBuilder? blogLayout,
- List<
IRouteSource> extraSources = const [], - List<
ApiEndpoint> apis = const [], - DbExecutor? db,
- EmailSender? onEmail,
- TokenVerifier? tokenVerifier,
- List<
FlenxStyle> globalStyles = const [], - List<
String> rawGlobalStyles = const [], - AdsConfig? ads,
- FlenxTracking? tracking,
- FlenxConsent? consent,
- String lang = 'pt-BR',
- int? port,
- String? primaryColor,
- String? primaryColorDark,
- String? faviconUrl,
- String? appleTouchIconUrl,
- List<
String> globalScripts = const [], - List<
Component> floatingButtons = const [], - List<
String> preloadImages = const [], - bool emitHtaccess = true,
Sobe o site com uma única chamada. routes é a fonte única: cada
FlenxRoute traz o SEO e o componente. Se blog for informado, /blog*
é resolvido sozinho. extraSources adiciona fontes dinâmicas de sitemap.
Implementation
static Future<void> run({
required ServerOptions options,
required SeoConfig seo,
required List<FlenxRoute> routes,
required Component notFound,
String? blog,
Blog? blogInstance,
List<BlogSource> blogSources = const [],
bool blogFromDb = false,
String blogTable = 'blog_posts',
BlogLayoutBuilder? blogLayout,
List<IRouteSource> extraSources = const [],
List<ApiEndpoint> apis = const [],
DbExecutor? db,
EmailSender? onEmail,
TokenVerifier? tokenVerifier,
// Estilos globais no padrão Flutter — lista de [FlenxStyle] (seletor +
// props tipadas: Color, EdgeInsets, FontWeight…). Ex.:
// `[FlenxStyle('h1', color: Colors.white, fontSize: 48)]`.
List<FlenxStyle> globalStyles = const [],
// Escape hatch de CSS puro: lista de strings (pode ser o conteúdo de um
// arquivo .css). Ex.: `['.x{height:48px}', await File('site.css').readAsString()]`.
List<String> rawGlobalStyles = const [],
AdsConfig? ads,
// Rastreamento/omnipresença: pixels e analytics (Ga4, MetaPixel, GoogleAds,
// BingUet, LinkedInInsight, FirebasePush, CustomPixel). Injeta os snippets
// no <head> + o dispatcher `flenx.track`. Nada é fixo — só o que passar.
FlenxTracking? tracking,
// Banner de consentimento (LGPD). Quando presente, o rastreamento espera o
// aceite (Consent Mode negado por padrão) antes de ativar os pixels.
FlenxConsent? consent,
String lang = 'pt-BR',
int? port,
// Cor da marca: define o token CSS `--primary` (e `--primary-dark`) usado
// por botões, links e destaques. Sem precisar escrever CSS.
String? primaryColor,
String? primaryColorDark,
// Favicon: emite <link rel="icon"> e <link rel="apple-touch-icon"> no <head>.
String? faviconUrl,
String? appleTouchIconUrl,
// Scripts globais (URLs) injetados em toda página com `defer`
// (ex.: analytics: `['/assets/js/gtag.js']`).
List<String> globalScripts = const [],
// Botões flutuantes globais (aparecem em todas as páginas) — ex.:
// `[FlenxFloatingButton.whatsapp(href: ...)]`.
List<Component> floatingButtons = const [],
// Imagens da dobra inicial (LCP) — preload com fetchpriority=high.
List<String> preloadImages = const [],
// Gera `.htaccess` (Apache/LiteSpeed) no build estático. Padrão: sim.
bool emitHtaccess = true,
}) async {
Jaspr.initializeApp(options: options);
final byPath = {for (final r in routes) r.meta.path: r};
final sources = <IRouteSource>[
StaticRouteSource([for (final r in routes) r.meta]),
...extraSources,
];
// Monta as fontes de posts (Markdown e/ou banco). Quando há fonte de banco,
// o blog é DINÂMICO: recarrega a cada request, então posts criados pelo
// admin em runtime aparecem sem reiniciar o servidor.
final blogSrcs = <BlogSource>[
if (blog != null) MarkdownBlogSource(blog),
...blogSources,
if (blogFromDb && db != null) DatabaseBlogSource(db, table: blogTable),
];
final dynamicBlog =
blogInstance == null && blogSrcs.any((s) => s is DatabaseBlogSource);
Blog? loaded = blogInstance;
if (loaded == null && blogSrcs.isNotEmpty && !dynamicBlog) {
loaded = await Blog.fromSources(blogSrcs);
}
if (loaded != null) {
sources.add(loaded.routeSource);
} else if (dynamicBlog) {
sources.add(DynamicBlogRouteSource(blogSrcs));
}
final registry = RouteRegistry(sources);
bool handlesBlog(String path) =>
path == '/blog' || path.startsWith('/blog/');
Future<Blog?> blogFor(String path) async {
if (!handlesBlog(path)) return null;
if (loaded != null) return loaded;
if (dynamicBlog) return Blog.fromSources(blogSrcs);
return null;
}
await FlenxServer(
seo: seo,
registry: registry,
apis: apis,
db: db,
onEmail: onEmail,
tokenVerifier: tokenVerifier,
notFound: notFound,
// Estilos tipados (FlenxStyle) convertidos em regras CSS.
styleRules: [for (final s in globalStyles) s.toRule()],
// CSS puro: tokens da paleta central (fonte única) + override da marca
// (--primary) + strings do app. Componentes usam `var(--token)`; qualquer
// projeto re-tematiza sobrescrevendo estes tokens.
rawStyles: [
FlenxPalette.rootCss,
if (primaryColor != null)
':root{--primary:$primaryColor'
'${primaryColorDark != null ? ';--primary-dark:$primaryColorDark;--primary-d:$primaryColorDark' : ''}}',
...rawGlobalStyles,
],
headExtra: [
// Rastreamento primeiro: o Consent Mode precisa vir antes do gtag.
if (tracking != null)
...tracking.headComponents(requireConsent: consent != null),
if (faviconUrl != null) link(rel: 'icon', href: faviconUrl),
if (appleTouchIconUrl != null)
link(rel: 'apple-touch-icon', href: appleTouchIconUrl),
for (final src in globalScripts) script(src: src, defer: true),
if (ads != null && ads.enabled)
script(
src: ads.loaderUrl,
async: true,
attributes: const {'crossorigin': 'anonymous'},
),
],
floatingButtons: [
...floatingButtons,
if (consent != null) consent,
],
preloadImages: preloadImages,
emitHtaccess: emitHtaccess,
lang: lang,
resolvePage: (path, query) async {
final blogNow = await blogFor(path);
if (blogNow != null && blogNow.handles(path)) {
final page = blogNow.pageFor(
path,
query: query['q'],
page: int.tryParse(query['page'] ?? '') ?? 1,
);
if (page == null) return null;
return PageResult(blogLayout != null ? blogLayout(page) : page);
}
final route = byPath[path];
if (route == null) return null;
return PageResult(
route.builder(RouteContext(path, query)),
island: route.island,
);
},
).start(port: port);
}